一次并发插入死锁带来的“教训”,我才清楚这些MySQL锁知识

最近遇到一个由于唯一性索引 , 导致并发插入产生死锁的场景 , 在分析死锁产生的原因时 , 发现这一块还挺有意思的 , 涉及到MySQL中不少的知识点 , 特此总结记录一下 。
 
一、MySql常见的锁谈到mysql的锁 , 可以说的就比较多了 , 比如行锁、表锁、页锁、元数据锁等 , 当然我们这里没打算把所有的都细列出来 , 我们这里主要针对行锁、gap锁进行拓展 , 以方便分析第二节中 , 为什么并发插入同样的数据会产生死锁的问题
0. 锁分类
我们最常说的锁 , 可以区分为共享锁(S)和排它锁(X) , 在mysql的innodb引擎中 , 为了解决幻读问题 , 引入了gap锁以及next key lock;除此之外 , 还有一种意向锁的 , 比如插入意向锁
本文将主要介绍的以下几种锁

  • 行锁(record lock): 请注意它是针对索引的锁(所以如果没有索引时 , 最终行锁就会导致整个表都会被锁住)
  • 共享锁(S Lock): 也叫读锁 , 共享锁之间不会相互阻塞(顾名思义)
  • 排它锁(X Lock): 也叫写锁 , 排它锁一次只能有一个session(或者说事务?)持有
  • 间隙锁(gap lock): 针对索引之间的间隙
  • Next-key锁(Next-key lock):可以简单理解为行锁 + 间隙锁
上面虽然介绍了几种锁的基本定义 , 但是什么时候是行锁 , 怎样获取共享锁 , 排它锁又是在哪些场景下会产生呢?gap lock/next key lock又是怎样解决幻读的呢?
下面所有的都是基于mysql5.7.22 innodb引擎 , rr隔离级别进行说明
1.共享锁与排它锁
下表介绍我们的实际使用的sql中 , 是否会使用锁 , 以及会产生什么锁
共享锁与排他锁区分
一次并发插入死锁带来的“教训”,我才清楚这些MySQL锁知识

文章插图
 
2. 行锁、表锁、gap锁、next-key锁区分
这几个的区分 , 主要就是看我们最终锁住的效果 , 比如
  • 没有索引 , 加S/X锁最终都是锁整表 (为啥?因为锁是针对索引而言的)
  • 根据主键/唯一键锁定确定的记录:行锁
  • 普通索引或者范围查询:gap lock / next key lock
行锁和gap锁之间最大的区别是:
  • 行锁针对确定的记录
  • 间隙锁是两个确定记录之间的范围; next key lock则是除了间隙还包括确定的记录
3. 实例演示
看上面的两个说明 , 自然就想在实际的case中操刀分析一下 , 不同的sql会产生什么样的锁效果
  • 针对表中一条确定的记录加X锁 , 是只有行锁嘛?
  • 针对表中多条确定的记录加X锁 , 又会怎样?
  • 针对表中一条不存在的记录加X锁 , 会有锁产生吗?如果是gap锁 , 那区间怎么定?
  • 针对范围加X锁 , 产生的gap锁范围怎么确定呢?
在分析上面几种case之前 , 我们得先有一个概念 , 锁是针对索引而言的 , 这一点非常非常重要
其次不同的索引 , 我们需要分别进行测试(其实就是唯一索引与普通索引)
3.1 表准备
接下来针对上面的四种场景 , 设计我们的测试用例 , 首先我们准备三张表
  • 无索引表 TN
  • 唯一索引表 TU
  • 普通索引表 TI
对应的表结构和初始化数据如下
CREATE TABLE `tn` (`id` int(11) unsigned NOT NULL,`uid` int(11) unsigned NOT NULL) ENGINE=InnoDB;CREATE TABLE `tu` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`uid` int(11) unsigned NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `u_uid` (`uid`)) ENGINE=InnoDB;CREATE TABLE `ti` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`uid` int(11) unsigned NOT NULL,PRIMARY KEY (`id`),KEY `u_uid` (`uid`)) ENGINE=InnoDB;INSERT INTO `tn` (`id`, `uid`) VALUES (1, 10), (5, 20), (10, 30);INSERT INTO `tu` (`id`, `uid`) VALUES (1, 10), (5, 20), (10, 30);INSERT INTO `ti` (`id`, `uid`) VALUES (1, 10), (5, 20), (10, 30);3.2 精确匹配
即我们的sql可以精确命中某条记录时 , 锁的情况如下:
一次并发插入死锁带来的“教训”,我才清楚这些MySQL锁知识

文章插图
 
请注意上面的结论 , 无索引时锁全表好理解 , 但是普通索引的TI表 , 居然还有一个[10, 30)的gap锁就有点超乎我们的想象了;
接下来我们验证一下


推荐阅读