Record Lock 行锁
单条索引记录上的锁,对索引进行加锁,而不是记录本身。如果没有索引,则对隐藏的聚集索引进行加锁。如果 sql 没有走索引,那么就会对每个聚集索引进行加锁
InnoDB 实现了标准的行级锁:
- 共享锁(S):允许事务读一行数据
- 排他锁(X):允许事务更新或删除一行数据
共享锁和排他锁的兼容性如下:
共享锁(S) | 排他锁(X) | |
---|---|---|
共享锁(S) | √ | x |
排他锁(X) | x | x |
X 锁与任何锁都不兼容,S 锁只与 S 锁兼容。S 锁和 X 锁都是行锁,兼容是指对同一记录(row)锁的兼容性情况。
(1)事务 A 获取了行 R 上的共享锁,事务 B 可以获取行 R 上的共享锁,如果想要获取排他锁,则必须等事务 A 释放共享锁后才能获取锁
(2)事务 A 获取了行 R 上的排他锁,事务 B 无法获取行 R 上的任何锁,必须等事务 A 释放排他锁后才能去获取锁
意向锁
InnoDB 支持多粒度的锁,允许事务在行级别和表级别的锁同时存在,为了支持在不同粒度上进行加锁操作,InnoDB 支持一种额外的锁方式,称为意向锁,意向锁意味着事务希望在更细粒度上进行加锁。InnoDB 中意向锁即为表级锁。
- 意向共享锁(IS):事务想要获得一张表中某几行的共享锁
- 意向排他锁(IX):事务想要获得一张表中某几行的排他锁
表级意向锁和行级锁的兼容性如下:
IS | IX | S | X | |
---|---|---|---|---|
IS | √ | √ | √ | x |
IX | √ | √ | x | x |
S | √ | x | √ | x |
X | x | x | x | x |
InnoDB 意向锁不会阻塞行级锁。
Gap Lock 间隙锁
对索引记录中的间隙进行加锁,不包括记录本身
作用是为了防止多个事务将记录插入到同一范围内,导致幻读问题的产生。
关闭 Gap Lock 方式:
- 事务隔离级别设置为 READ COMMITTED
- 将参数
innodb_locks_unsafe_for_binlog
设置为1
Next-Key Lock
Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身
例如,一个索引有10,11,13,20这四个值,那么该索引可能被 Next-Key Locking的区间为:
(-∞,10],(10,11],(11,13],(13,20],(20,+∞)
当查询的索引含有唯一属性时,InnoDB 存储引擎会对Next-Key Lock进行优化,降级为 Record Lock,仅锁住索引本身,而不是范围
辅助索引:对于辅助索引,InnoDB存储引擎还会对辅助索引下一个键值加上 gap lock
示例演示
数据准备
有如下数据的一张表,a设置为了主键,b设置了辅助索引
a | b | c |
---|---|---|
1 | 1 | 1 |
4 | 4 | 4 |
8 | 8 | 8 |
15 | 15 | 15 |
范围查询
- 主键索引,唯一索引,辅助索引
执行 a < 4 的查询,锁住 a 字段区间为 (-∞,4] 的数据,包含 4 这个索引记录。
# 事务A,查询 a < 4 的数据
select * from t where a<4 for update;
# 事务B,插入一条数据,被阻塞
insert into t values(-1,-1,-1);
# 事务B,插入一条数据,被阻塞
insert into t values(2,2,2);
# 事务B,更新主键值a=4的数据,被阻塞
update t set c=5 where a=4;
# 事务B,插入 a > 4的数据,成功
insert into t values(5,5,5);
等值查询
- 主键索引,唯一索引
执行 a = 4 的查询,锁住索引=4的记录
# 事务A,查询 a = 4 的数据
select * from t where a=4 for update;
# 事务B,插入一条数据,成功
insert into t values(2,2,2);
# 事务B,插入一条数据,成功
insert into t values(5,5,5);
# 事务B,更新主键值a=4的数据,被阻塞
update t set c=5 where a=4;
- 辅助索引
执行 b = 4 的查询,锁住b字段区间(1,4) 、4、 (4,8) 的数据,也就是 (1,8)区间的数据
# 事务A,查询 b = 4 的数据
select * from t where b=4 for update;
# 事务B,更新字段b=1的数据,成功
update t set c=5 where b=1
# 事务B,更新字段b=8的数据,成功
update t set c=5 where b=8;
# 事务B,插入一条数据,成功
insert into t values(-1,-1,-1);
# 事务B,插入一条数据,阻塞
insert into t values(2,2,2);
# 事务B,插入一条数据,阻塞
insert into t values(5,5,5);
死锁
两个或两个以上的事务在执行的过程中,因争夺资源而造成的一种互相等待的现象。
解决死锁办法:
- 超时回滚:当两个事务相互等待时,当一个等待时间超过设置的阈值,其中一个事务进行回滚,另一个等待的事务就能继续进行。参数
innodb_lock_wait_timeout
设置超时时间 - 采用 wait-for graph(等待图)的方式进行死锁检测,InnoDB也使用这种方式进行主动检测,采用深度优先的算法实现,发现图存在回路,则判定存在死锁,回滚 undo 量较小的事务。