mysql锁-innodb间隙锁死锁分析

前言:
在上篇里分析了一次mysql死锁问题,后来又深入研究了下死锁的其他场景,innodb间隙锁场景下也可能会发生死锁,所以进一步巩固下间隙锁的知识

gap锁定义:

1.gap就是索引树中插入新记录的空隙
2.相应的gap锁就是加在gap上的锁

gap锁作用:

防止幻读,通过间隙锁阻止特定条件的新记录的插入,后面单独就那些验证幻读现象

注意:

1. 只在REPEATABLE READ隔离级别下的特定操作才会取得gap lock
2. UPDATE/DELETE/SELECT FOR UPDATE时,除了对唯一索引的唯一搜索外都会获取gap锁,也就是说主键或唯一索引的搜索不会获取间隙锁,当然如果查询条件还包含非唯一索引,那么还是会获取间隙锁

模拟间隙锁死锁场景:

一、前置条件:

1.创建表:

CREATE TABLE `test_gap_table` (
  `primary_no` char(16) NOT NULL COMMENT '主键号',
  `index_no` char(16) NOT NULL COMMENT '索引号',
  PRIMARY KEY (`primary_no`),
  key(`index_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试间隙表';

2.插入测试数据:

INSERT INTO `test_gap_table` VALUES ('1', 'a');
INSERT INTO `test_gap_table` VALUES ('3', 'c');
INSERT INTO `test_gap_table` VALUES ('5', 'd');
INSERT INTO `test_gap_table` VALUES ('7', 'e');
二、模拟死锁:
  1. 开启事务1:
##开启事务1
START TRANSACTION;
## 获取间隙锁,锁定范围(a,d)
UPDATE test_gap_table SET index_no='c' where index_no='c';
## 休息10秒,执行事务2
SELECT SLEEP(10);
## 执行insert,插入到事务2的间隙锁锁定范围中
INSERT INTO test_gap_table(primary_no, index_no) VALUES('7', 'e');

截图:
mysql锁-innodb间隙锁死锁分析_第1张图片

  1. 开启事务2:
##开启事务2
START TRANSACTION;
## 获取间隙锁,锁定范围(d, d后面的无穷大)
UPDATE test_gap_table SET index_no='e' where index_no='e';
## 执行insert,插入到事务1的间隙锁锁定范围中
INSERT INTO test_gap_table(primary_no, index_no) VALUES('2', 'b');

截图:
mysql锁-innodb间隙锁死锁分析_第2张图片

三、结果:

事务1休息完十秒后,事务2出现死锁:
mysql锁-innodb间隙锁死锁分析_第3张图片

四、死锁分析:
  1. 因为index_no是非唯一索引,所以执行事务1更新语句时获取了间隙锁,锁定范围是非唯一索引的上下区间,即(a,d)
  2. 事务1休息,执行事务2更新语句获取间隙锁,锁定范围为(d, d后面的无穷大),然后执行事务2insert语句,因为事务1锁定了(a,d)范围,所以insert操作会等待事务1释放间隙锁
  3. 事务1休息结束,执行insert语句,恰好插入的数据在事务2锁定的间隙锁范围,这样就导致事务1和事务2循环等待了,死锁发生
五、间隙锁锁定原理:
  1. 执行update/delete/select for update,会产生间隙锁
  2. innodb会根据更新或查询的索引条件,寻找非唯一索引的间隙锁的上下区间,例如:
    UPDATE test_gap_table SET index_no=‘c’ where index_no=‘c’;
    因为index_no为非唯一索引,所以寻找’c’字符在表test_gap_table中记录的上下区间,上区间为:a,下区间为:d
  3. 如果插入数据在区间之内,则无法插入,因为被间隙锁锁定;如果插入数据在区间的边界值,此时需要根据主键来判断锁定范围,接着2中例子:
    1):如果index_no是a,则取a的最大主键值为’1’,只要插入的主键<'1’则可以自由插入,主键>‘1’全部锁定无法插入
    2):如果index_no是d,则取d的最小主键值为’5’,只要插入的主键>'5’则可以自由插入,主键<'5’全部锁定无法插入

模拟rr隔离级别下幻读:

1.创建表:

CREATE TABLE `test_rr_unreal` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `index_no` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

2.插入数据:

INSERT INTO `test_rr_unreal` VALUES ('1', '100');

3.在mysql命令行下开启一个事务A,执行查询操作:
mysql锁-innodb间隙锁死锁分析_第4张图片
4.在mysql命令行下开启另一个session,执行更新操作:
在这里插入图片描述
5.在事务A下,执行更新操作:
在这里插入图片描述
6.在事务A下,执行查询操作:
mysql锁-innodb间隙锁死锁分析_第5张图片

结果:

在事务A中,预期是基于查询结果101,但是被另一个事务的更新操作影响到了最终预期结果,违反了REPEATABLE READ的承诺,看到了事务开始后其它事务的并发更新,出现幻读。

分析:

虽然rr隔离级别下看到的是事务开始时的快照,在update/delete操作下,是可以看到另一个事务提交的操作的。

你可能感兴趣的:(mysql,问题分析)