ToB企服应用市场:ToB评测及商务社交产业平台

标题: InnoDB锁初探(一):锁分类和RR不同场景下的锁机制 [打印本页]

作者: 刘俊凯    时间: 2023-6-21 22:49
标题: InnoDB锁初探(一):锁分类和RR不同场景下的锁机制
Mysql数据库锁(Innodb)

数据库锁是Mysql实现数据一致性的基础之一,是在事务的基础之上,基于Mysql Server层或存储引擎层实现的。
锁日志

前置条件:
  1. set GLOBAL innodb_status_output=ON;  
  2. set GLOBAL innodb_status_output_locks=ON;
复制代码
查看语句:
  1. show engine innodb status\G;
复制代码
锁分类

表锁与行锁

按照锁的粒度,可以分为表锁和行锁
共享锁与排他锁

意向锁

记录锁(Record Lock)

间隙锁(Gap Lock)

临键锁(Next-Key Locks)

插入意向锁(Insert Intention Locks)

自增锁(AUTO-INC Locks)

元数据锁(metadata lock)

锁解读

RR下的有二级索引的情况
  1. CREATE TABLE `a` (
  2.   `a` int(11) NOT NULL,
  3.   `b` int(11) DEFAULT NULL,
  4.   `c` int(11) DEFAULT NULL,
  5.   `d` int(11) DEFAULT NULL,
  6.   `aaa` bigint(20) DEFAULT '0',
  7.   PRIMARY KEY (`a`),
  8.   UNIQUE KEY `idx_b` (`b`),
  9.   KEY `idx_c` (`c`)
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
  1. select * from a WHERE c = 7 for UPDATE;
复制代码
  1. MySQL thread id 10, OS thread handle 139897622177536, query id 377 172.18.0.1 wss
  2. 对a表添加意向排他锁
  3. TABLE LOCK table `demo`.`a` trx id 480393 lock mode IX
  4. 对a表的idx_c二级索引加临键锁
  5. RECORD LOCKS space id 97 page no 5 n bits 72 index idx_c of table `demo`.`a` trx id 480393 lock_mode X
  6. Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  7. 二级索引idx_c的7加锁
  8. 0: len 4; hex 80000007; asc     ;;
  9. 主键索引上的3也会被加锁
  10. 1: len 4; hex 80000003; asc     ;;
  11. 对主键索引加记录锁,对3进行加锁
  12. RECORD LOCKS space id 97 page no 3 n bits 72 index PRIMARY of table `demo`.`a` trx id 480393 lock_mode X locks rec but not gap
  13. Record lock, heap no 3 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
  14. 第一个字段是主键,被加锁
  15. 0: len 4; hex 80000003; asc     ;;
  16. 最近一次被更新的事务id
  17. 1: len 6; hex 000000009c29; asc      );;
  18. 回滚指针
  19. 2: len 7; hex be00000147011c; asc     G  ;;
  20. 该行第2、3、4、5个字段
  21. 3: len 4; hex 80000005; asc     ;;
  22. 4: len 4; hex 80000007; asc     ;;
  23. 5: len 4; hex 80000009; asc     ;;
  24. 6: len 8; hex 8000000000000000; asc         ;;
  25. 对idx_c索引树上加间隙锁
  26. RECORD LOCKS space id 97 page no 5 n bits 72 index idx_c of table `demo`.`a` trx id 480393 lock_mode X locks gap before rec
  27. Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  28. 二级索引idx_c上对9加锁
  29. 0: len 4; hex 80000009; asc     ;;
  30. 主键索引上的5也会被锁住
  31. 1: len 4; hex 80000005; asc     ;;
复制代码
RR下的无二级索引的情况
  1. CREATE TABLE `tm` (
  2.   `i` int(11) DEFAULT NULL
  3. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
  1. SELECT * FROM tm WHERE i = 1 FOR UPDATE;
复制代码
  1. MySQL thread id 25, OS thread handle 139897622718208, query id 556 172.18.0.1 wss
  2. 对tm表添加意向排他锁
  3. TABLE LOCK table `demo`.`tm` trx id 480412 lock mode IX
  4. 由于表定义没有显示的索引,而InnoDB又是索引组织表,会自动创建一个索引,这里面叫index GEN_CLUST_INDEX  
  5. 由于没有索引,那么会对每条记录都加上临键锁
  6. RECORD LOCKS space id 110 page no 3 n bits 80 index GEN_CLUST_INDEX of table `demo`.`tm` trx id 480412 lock_mode X
  7. supremum 指的是页里面的最后一条记录(伪记录,通过select查不到,并不是真实记录);还有Infimum表示页面中的第一个记录(伪记录)
  8. 通过supremum 锁住index GEN_CLUST_INDEX的最大值到正无穷大的区间,这样就可以锁住全部记录,以及全部间隙,相当于表锁
  9. Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
  10. 0: len 8; hex 73757072656d756d; asc supremum;;
  11. Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  12. 0: len 6; hex 000000000300; asc       ;;
  13. 1: len 6; hex 00000007548e; asc     T ;;
  14. 2: len 7; hex ea000001960110; asc        ;;
  15. 3: len 4; hex 80000001; asc     ;;
  16. Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  17. 0: len 6; hex 000000000301; asc       ;;
  18. 1: len 6; hex 00000007548e; asc     T ;;
  19. 2: len 7; hex ea00000196011e; asc        ;;
  20. 3: len 4; hex 80000002; asc     ;;
  21. Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  22. 0: len 6; hex 000000000302; asc       ;;
  23. 1: len 6; hex 00000007548e; asc     T ;;
  24. 2: len 7; hex ea00000196012c; asc       ,;;
  25. 3: len 4; hex 80000003; asc     ;;
  26. Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  27. 0: len 6; hex 000000000303; asc       ;;
  28. 1: len 6; hex 00000007548e; asc     T ;;
  29. 2: len 7; hex ea00000196013a; asc       :;;
  30. 3: len 4; hex 80000004; asc     ;;
  31. Record lock, heap no 6 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  32. 0: len 6; hex 000000000304; asc       ;;
  33. 1: len 6; hex 00000007548e; asc     T ;;
  34. 2: len 7; hex ea000001960148; asc       H;;
  35. 3: len 4; hex 80000005; asc     ;;
  36. Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  37. 0: len 6; hex 000000000305; asc       ;;
  38. 1: len 6; hex 00000007548e; asc     T ;;
  39. 2: len 7; hex ea000001960156; asc       V;;
  40. 3: len 4; hex 80000005; asc     ;;
  41. Record lock, heap no 8 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  42. 0: len 6; hex 000000000306; asc       ;;
  43. 1: len 6; hex 00000007548e; asc     T ;;
  44. 2: len 7; hex ea000001960164; asc       d;;
  45. 3: len 4; hex 80000005; asc     ;;
  46. Record lock, heap no 9 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  47. 0: len 6; hex 000000000307; asc       ;;
  48. 1: len 6; hex 00000007548e; asc     T ;;
  49. 2: len 7; hex ea000001960172; asc       r;;
  50. 3: len 4; hex 80000005; asc     ;;
复制代码
锁算法

自增锁

​        自增列的维护与数据的新增有关,任何产生新数据的语句都可以称为”Insert like“,大致分3种,分别是simple insert、bulk inserts、mixed-mode inserts
simple insert:插入记录的行数时确定的,比如:insert into values、replace
bulk inserts:插入的记录行数不能马上确定的,比如:insert ...  select ...  ,replace ... select  和load data
mixed-mode inserts:部分自增列的值给定或者不给定,比如INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');和INSERT ... ON DUPLICATE KEY UPDATE
死锁

自增锁死锁常常出现在数据迁移过程中。常见的数据迁移大多以双写来实现,类似一个进程负责从旧表往新表写(insert ...  select ...),而应用程序则继续往新表写,此时新表可能会发生死锁。
锁模式

​                这是默认的锁模式,当发生bulk inserts时,会产生一个表级的自增锁直到语句执行结束,注意不是事务结束。对于simple insert,则使用轻量锁,只要获取相应的auto increment就释放锁,不会等待语句结束。当表被加上自增锁后,这种轻量锁不会加锁成功,会等待。
​                优点是性能较好,缺点还是会产生表级的自增锁,因为要保证自增id的连续性,防止bulk inserts时,被其他insert 语句抢走 auto increment值。
​                当进行bulk insert 时,不会产生表级别的自增锁,因为他是允许其他insert 插入的,新增一条记录,插入分配一个auto increment值,不会预分配。
​                优点是性能较好,缺点是一次bulk inserts 产生的自增列并不是连续的,同时SBR模式下的主从复制可能会产生数据不一致错误,该错误可以通过将主从复制改为RBR模式。
​                PS:SBR模式的主从复制:binlog格式以statement的日志格式;RBR模式的主从复制:binlog格式以基于行(Row)的日志格式(推荐)。
优化

如果binlog-format是row模式的,而且不关心一条bulk insert的自增列的值连续且提交顺序与自增列值大小的顺序一致,那么可以设置innodb_autoinc_lock_mode = 2 来提高性能
一条bulk insert 自增列是否连续有时候会影响分页查询,有时候为了解决深分页查询问题,会采用每次分页查询的最大值来进行分页,比如
  1. select * from xx where id>1 limit N
  2. select * from xx where id>1+N limit N
  3. select * from xx where id>1+N+N limit N
复制代码
当id=101的记录先提交,该记录的值刚好是当前页的最大值,此时id=100数据被提交,那么下次分页查询会从101开始查询,就会造成这次翻页的数据存在缺失的情况。如果分页查询中包含oder by id的查询或者有and create_time < (now() - INTERVAL 5 second),那么可以通过往前翻页来找到,但是归根结底当前分页的数据需要等待100数据被提交后刷新分页来解决。
通用锁

<ul>锁是在索引上实现的

假设有一个key,有5条记录, 1,3,5,7,9.  如果where idnext-rec: 表示M的下一条记录
M->pre-rec: 表示M的前一条记录
</ol>第一轮总结

​        (M->pre-rec,M],(M,M->next-rec]
​        [M], next-lock 降级为 record locks
​        (M->pre_rec,M],(M,M->next-rec]....(∞]
第二轮总结合并

最后的疑问和总结

为什么要对M->next-rec 或者  M->pre-rec ?
因为为了防止幻读。

RR下的Insert锁机制

Insert 的流程(没有唯一索引的情况): insert N

Insert 的流程(有唯一索引的情况): insert N


参考资料

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4