内容简介:session A持有的锁:
mysql> SELECT VERSION(); +-----------+ | version() | +-----------+ | 8.0.12 | +-----------+ mysql> SHOW VARIABLES LIKE '%transaction_isolation%'; +-----------------------+-----------------+ | Variable_name | Value | +-----------------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +-----------------------+-----------------+
加锁规则
- 基本原则
- 加锁的 基本单位 是
Next-Key Lock - 遍历过程中 被访问到的对象 才有可能被加锁
- 加锁的 基本单位 是
- 等值查询的优化
- 如果遍历的是 唯一索引 (聚簇索引)且能 等值命中 ,
Next-Key Lock会降级为Row Lock - 向 右 遍历到 第一个不满足等值条件 的时候,
Next-Key Lock会降级为Gap Lock
- 如果遍历的是 唯一索引 (聚簇索引)且能 等值命中 ,
- bug
- 在 唯一索引 (聚簇索引)上的 范围查询 ,会访问到 不满足条件的第一个值 为止
表初始化
CREATE TABLE `t` ( `id` INT(11) NOT NULL, `c` INT(11) DEFAULT NULL, `d` INT(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; INSERT INTO t VALUES (0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
唯一索引
等值查询 – 不命中降级
Next-Key Lock 降级为 Gap Lock
| session A | session B | session C |
|---|---|---|
| BEGIN; UPDATE t SET d=d+1 WHERE id=7; |
||
| INSERT INTO t VALUES(1,1,1); (Query OK) |
||
| INSERT INTO t VALUES(8,8,8); (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=5; (Query OK) |
||
| UPDATE t SET d=d+1 WHERE id=10; (Query OK) |
-- sesson B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X,GAP | X,GAP | +--------------+-------------+-------------------+--------------------+
session A持有的锁: PRIMARY:Gap Lock:(5,10)
范围查询 – 起点降级
Next-Key Lock 降级为 Row Lock
| session A | session B | session C |
|---|---|---|
| BEGIN; SELECT * FROM t WHERE id>=10 AND id<11 FOR UPDATE; |
||
| INSERT INTO t VALUES (8,8,8); (Query OK) |
||
| INSERT INTO t VALUES (13,13,13); (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=10; (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=15; (Blocked) |
-- sesson B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X,GAP | X | +--------------+-------------+-------------------+--------------------+ -- sesson C Blocked 1 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+ -- sesson C Blocked 2 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+
条件会拆分成 =10 ( Row Lock )和 >10 & <11 ,session A持有的锁:: PRIMARY:X Lock:10 + PRIMARY:Next-Key Lock:(10,15]
范围查询 – 尾点延伸
直到遍历到 第一个不满足的值 为止
| session A | session B | session C |
|---|---|---|
| BEGIN; SELECT * FROM t WHERE id>10 AND id<=15 FOR UPDATE; |
||
| INSERT INTO t VALUES (16,16,16); (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=20; (Blocked) |
-- sesson B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X,GAP | X | +--------------+-------------+-------------------+--------------------+ -- sesson C Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+
session A持有的锁: PRIMARY:Next-Key Lock:(10,15] + PRIMARY:Next-Key Lock:(15,20]
非唯一索引
等值查询 – LOCK IN SHARE MODE
| session A | session B | session C |
|---|---|---|
| BEGIN; SELECT id FROM t WHERE c=5 LOCK IN SHARE MODE; |
||
| INSERT INTO t VALUES (7,7,7); (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=5; (Query OK) |
||
| UPDATE t SET d=d+1 WHERE c=10; (Query OK) |
-- Using index:覆盖索引 mysql> EXPLAIN SELECT id FROM t WHERE c=5 LOCK IN SHARE MODE; +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | t | NULL | ref | c | c | 5 | const | 1 | 100.00 | Using index | +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+ -- sesson B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X,GAP | S,GAP | +--------------+-------------+-------------------+--------------------+
session A持有的锁: c:Next-Key Lock:(0,5] + c:Gap Lock:(5,10)
等值查询 – FOR UPDATE
LOCK IN SHARE MODE 只会锁住 覆盖索引 ,而 FOR UPDATE 会同时给 聚簇索引 上 满足条件的行 加上 X Lock
| session A | session B |
|---|---|
| BEGIN; SELECT id FROM t WHERE c=5 FOR UPDATE; |
|
| UPDATE t SET d=d+1 WHERE id=5; (Blocked) |
-- sesson B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+
session A持有的锁: c:Next-Key Lock:(0,5] + c:Gap Lock:(5,10) + PRIMARY:X Lock:5
等值查询 – 绕过覆盖索引
无法利用覆盖索引,就必须 回表 ,与上面 FOR UPDATE 的情况一致
| session A | session B |
|---|---|
| BEGIN; SELECT d FROM t WHERE c=5 LOCK IN SHARE MODE |
|
| UPDATE t SET d=d+1 WHERE id=5; (Blocked) |
-- Extra=NULL:绕过覆盖索引,需要回表 mysql> EXPLAIN SELECT d FROM t WHERE c=5 LOCK IN SHARE MODE; +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | t | NULL | ref | c | c | 5 | const | 1 | 100.00 | NULL | +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+ -- sesson B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | S | +--------------+-------------+-------------------+--------------------+
session A持有的锁: c:Next-Key Lock:(0,5] + c:Gap Lock:(5,10) + PRIMARY:S Lock:5
等值查询 – 相同的值
-- c=10有两行,两行之间也存在Gap INSERT INTO t VALUES (30,10,30);
DELETE 语句的加锁逻辑与 SELECT...FOR UPDTAE 是类似的
| session A | session B | session C |
|---|---|---|
| BEGIN; DELETE FROM t WHERE c=10; |
||
| INSERT INTO t VALUES (12,12,12); (Blocked) |
||
| UPDATE t SET d=d+1 WHERE c=5; (Query OK) |
||
| UPDATE t SET d=d+1 WHERE c=15; (Query OK) |
||
| UPDATE t SET d=d+1 WHERE id=5; (Query OK) |
||
| UPDATE t SET d=d+1 WHERE id=15; (Query OK) |
||
| UPDATE t SET d=d+1 WHERE id=10; (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=30; (Blocked) |
-- session B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X,GAP | X,GAP | +--------------+-------------+-------------------+--------------------+ -- session C Blocked 1 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+ -- session C Blocked 2 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+
session A在二级索引c上的加锁效果如下所示
session A持有的锁
-
c:Next-Key Lock:((c=5,id=5),(c=10,id=10)]+c:Gap Lock:((c=10,id=10),(c=15,id=15)) -
PRIMARY:X Lock:10+PRIMARY:X Lock:30
等值查询 – LIMIT
-- 与上面“相同的值”一样 INSERT INTO t VALUES (30,10,30);
| session A | session B | session C |
|---|---|---|
| BEGIN; DELETE FROM t WHERE c=10 LIMIT 2; |
||
| INSERT INTO t VALUES (12,12,12); (Query OK) |
||
| UPDATE t SET d=d+1 WHERE id=10; (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=30; (Blocked) |
-- session C Blocked 1 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+ -- session C Blocked 2 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+
在遍历到 (c=10,id=30) 这一行记录后,已经有两行记录满足条件, 循环结束 ,session A在二级索引c上的加锁效果如下所示
session A持有的锁
-
c:Next-Key Lock:((c=5,id=5),(c=10,id=10)]+c:Next-Key Lock:((c=10,id=10),(c=10,id=30)] -
PRIMARY:X Lock:10+PRIMARY:X Lock:30
因此在删除数据时,尽量加上 LIMIT ,可以 控制删除数据的条数 ,也可以 减少加锁的范围
等值查询 – Gap Lock死锁
| session A | session B |
|---|---|
| BEGIN; SELECT id FROM t WHERE c=10 LOCK IN SHARE MODE; |
|
| UPDATE t SET d=d+1 WHERE c=10; (Blocked) |
|
| INSERT INTO t values (8,8,8); | |
| ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
由于二级索引 c 是 非唯一索引 ,因此没法降级为 行锁
session A持有的锁
-
c:Next-Key Lock:(5,10]+c:Next-Key Lock:(10,15] -
PRIMARY:S Lock:10
session B首先尝试持有 c:Next-Key Lock:(5,10] ,分 两阶段
c:Gap Lock:(5,10) c:X Lock:10
session A尝试插入 (8,8,8) ,被session B的 c:Gap Lock:(5,10) 阻塞,系统检测到死锁并回滚session B
-- session B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X | S | +--------------+-------------+-------------------+--------------------+
范围查询
| session A | session B | session C |
|---|---|---|
| BEGIN; SELECT * FROM t WHERE c>=10 AND c<11 FOR UPDATE; |
||
| INSERT INTO t VALUES (8,8,8); (Blocked) |
||
| UPDATE t SET d=d+1 WHERE id=10; (Blocked) |
||
| UPDATE t SET d=d+1 WHERE c=15; (Blocked) |
-- session B Blocked mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X,GAP | X | +--------------+-------------+-------------------+--------------------+ -- session C Blocked 1 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | X | +--------------+-------------+-------------------+--------------------+ -- session C Blocked 2 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X | X | +--------------+-------------+-------------------+--------------------+
由于二级索引 c 是 非唯一索引 ,因此没法降级为 行锁
session A持有的锁
-
c:Next-Key Lock:(5,10]+c:Next-Key Lock:(10,15] -
PRIMARY:X Lock:10
ORDE BY DESC
| session A | session B |
|---|---|
| BEGIN; SELECT * FROM t WHERE c>=15 AND c <=20 ORDER BY c DESC LOCK IN SHARE MODE; |
|
| INSERT INTO t VALUES (6,6,6); (Blocked) |
|
| INSERT INTO t VALUES (21,21,21); (Blocked) |
|
| UPDATE t SET d=d+1 WHERE id=10; (Query OK) |
|
| UPDATE t SET d=d+1 WHERE id=25; (Query OK) |
|
| UPDATE t SET d=d+1 WHERE id=15; (Blocked) |
|
| UPDATE t SET d=d+1 WHERE id=20; (Blocked) |
-- session B Blocked 1 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X,GAP | S | +--------------+-------------+-------------------+--------------------+ -- session B Blocked 2 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | c | RECORD | X,GAP | S,GAP | +--------------+-------------+-------------------+--------------------+ -- session B Blocked 3 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | S | +--------------+-------------+-------------------+--------------------+ -- session B Blocked 4 mysql> SELECT locked_index,locked_type,waiting_lock_mode,blocking_lock_mode FROM sys.innodb_lock_waits WHERE locked_table='`test`.`t`'; +--------------+-------------+-------------------+--------------------+ | locked_index | locked_type | waiting_lock_mode | blocking_lock_mode | +--------------+-------------+-------------------+--------------------+ | PRIMARY | RECORD | X | S | +--------------+-------------+-------------------+--------------------+
-
ORDE BY DESC,首先找到第一个满足c=20的行,session A持有锁:c:Next-Key Lock:(15,20] - 由于二级索引
c是 非唯一索引 ,继续向 右 遍历,session A持有锁:c:Gap Key Lock:(20,25) - 向 左 遍历,
c=15,session A持有锁:c:Next-Key Lock:(10,15] - 继续向 左 遍历,
c=10,session A持有锁:c:Next-Key Lock:(5,10] - 上述过程中,满足条件的主键为
id=15和id=20,session A持有 聚簇索引 上对应行的S Lock - 总结,session A持有的锁
-
c:Next-Key Lock:(5,10]+c:Next-Key Lock:(10,15]+c:Next-Key Lock:(15,20]+c:Gap Key Lock:(20,25) -
PRIMARY:S Lock:15+PRIMARY:S Lock:20
-
RR与RC
- RR隔离级别
- 遵守 两阶段 协议,所有 加锁的资源 都是在事务 提交 或 回滚 时才释放
- RC隔离级别
- 执行过程加上的 行锁 ,在 语句执行完成后 ,就要把 “不满足条件的行”上的行锁直接释放 ,无需等待事务提交或回滚
- 锁范围更小 , 锁定时间更短 ,这是很多业务选择RC的一个原因
参考资料
《MySQL实战45讲》
转载请注明出处:http://zhongmingmao.me/2019/02/15/mysql-rr-row-lock/
访问原文「MySQL -- RR的行锁」获取最佳阅读体验并参与讨论
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
写给大家看的设计书(第3版)
[美] Robin Williams / 苏金国、刘亮 / 人民邮电出版社 / 2009-1 / 49.00元
这本书出自一位世界级设计师之手。复杂的设计原理在书中凝炼为亲密性、对齐、重复和对比4 个基本原则。作者以其简洁明快的风格,将优秀设计所必须遵循的这4 个基本原则及其背后的原理通俗易懂地展现在读者面前。本书包含大量的示例,让你了解怎样才能按照自己的方式设计出美观且内容丰富的产品。 此书适用于各行各业需要从事设计工作的读者,也适用于有经验的设计人员。一起来看看 《写给大家看的设计书(第3版)》 这本书的介绍吧!