锁
- 当多个事务并发访问或修改同一份数据时,保证数据一致性,避免重复修改、丢失更新、或者发生其他并发问题
数据库锁通常由数据库自动管理,开发中主要需要关注:
- 锁住什么范围;
- 锁的读写权限;
- 锁什么时候释放;
- 是否会产生阻塞和死锁;
- 是否真的需要显式加锁
数据库锁分类
- 锁的权限:共享锁、排他锁
- 共享锁和排他锁由数据库自动管理,不需要你手动配置
- 并发控制策略:乐观锁
- 锁的生命周期:事务锁、会话级锁
- 锁的范围:行锁、表锁
最常见的锁
- 排他锁 X Lock
- 只要执行修改语句,数据库通常会自动加排他锁:
- 行锁
排他锁
- 排他锁主要用于修改
- 当前事务修改这条数据时,其他事务不能同时修改它。
- 一般不需要手动配置,数据库自动获取,并在事务提交或回滚后释放
# 当前事务正在修改,其他事务不能同时修改。
UPDATE jobs
SET status = 'processing'
WHERE id = 100;
共享锁
- 多个事务可以一起持有,主要用于读取
行锁
- 锁一行或几行,只锁住表中的某一条或几条记录
BEGIN;
# 数据库就会锁住 jobs 表中 id = 100 的这一行
UPDATE jobs
SET status = 'processing'
WHERE id = 100;
COMMIT;
表锁
- 锁住整张表
- 不适用于并发场景,在业务场景种很少使用
BEGIN;
# 锁住后,其他事务对 jobs 表的很多操作都可能等待
LOCK TABLE jobs IN EXCLUSIVE MODE;
COMMIT;
数据库锁如何配置
数据库会自动选择和管理锁。开发中主要配置:
- 锁等待时间
- 事务超时时间
- 死锁检测
- 隔离级别
- 数据库连接超时
- 长事务限制
事务锁的典型场景
- 使用 PostgreSQL 任务表时,多个 Worker 并发领取任务。
常见锁配置
锁语句
| 语法 | 含义 | 常见程度 |
|---|---|---|
FOR UPDATE | 查询并锁住,准备修改 | 非常常用 |
FOR SHARE | 查询并共享锁定 | 较少使用 |
NOWAIT | 获取不到锁立即报错 | 特定场景 |
SKIP LOCKED | 跳过已锁记录 | Worker 队列常用 |
理解语句
FOR UPDATE
= 查询并锁住,准备修改
NOWAIT
= 锁不到就立即失败
SKIP LOCKED
= 被别人锁住就跳过
FOR UPDATE语句
- 查询后准备修改,先锁住这些记录,这是开发中最常见的显式加锁方式
BEGIN;
SELECT *
FROM jobs
WHERE id = 100
FOR UPDATE;
UPDATE jobs
SET status = 'processing'
WHERE id = 100;
COMMIT;
常用语法组合速查
| 语法 | 含义 |
|---|---|
BEGIN | 开启事务 |
COMMIT | 提交事务并释放锁 |
ROLLBACK | 回滚事务并释放锁 |
SELECT ... FOR UPDATE | 查询并加行级修改锁 |
SELECT ... FOR SHARE | 查询并加共享锁 |
FOR UPDATE NOWAIT | 获取不到锁立即失败 |
FOR UPDATE SKIP LOCKED | 跳过已锁记录 |
UPDATE ... | 自动获取写锁 |
DELETE ... | 自动获取删除相关锁 |
LOCK TABLE ... | 手动锁表 |
SAVEPOINT | 创建事务保存点 |
SET TRANSACTION ISOLATION LEVEL | 设置事务隔离级别 |