RR 死锁一侧

发布时间 2023-08-01 09:28:29作者: 章怀柔

CREATE TABLE `t8` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `d_id` varchar(40) NOT NULL DEFAULT '',
  `b_id` varchar(40) NOT NULL DEFAULT '',
  `is_dropped` tinyint(1) NOT NULL DEFAULT '0',
  `u_c` varchar(10) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `DealerAndBrokerAndDropped` (`d_id`,`b_id`,`is_dropped`)
) ENGINE=InnoDB ;

insert into t8 values(1,1,1,0,'a');
insert into t8 values(2,2,2,0,'a');
insert into t8 values(3,3,3,0,'a');
insert into t8 values(4,4,4,0,'a');

 

执行语句如下:

S1S2
begin  
select u_c from t8 where d_id='1' and b_id='1' and is_dropped=0 for update;  
  select u_c from t8 where d_id='1' and b_id='1' and is_dropped=0 for update; 处于堵塞状态
update t8 set u_c='b' where d_id='1' and b_id='1'; —此时触发死锁 S2回滚

 

仔细分析我们会发现trx id 5679最后被堵塞需要获取的锁为(lock_mode X waiting),堵塞发生在索引DealerAndBrokerAndDropped 上,也就是这是一个next key lock 且需要获取的模式为LOCK_X,处于等待状态。而我们来看trx id 5679前面获取的锁是什么呢?显然可以看到为(lock_mode X locks rec but not gap),获取发生在索引DealerAndBrokerAndDropped 上,也就是这是一个key lock且获取模式为LOCK_X。但是我们需要知道DealerAndBrokerAndDropped明明是一个唯一索引,获取key lock我们很容易理解,但是为什么也会出现获取next key lock呢?这个问题我们先放一下,先来分析一下整个死锁的产生的过程S1(select操作)
通过唯一性索引定位索引数据获取了唯一索引DealerAndBrokerAndDropped 上的
LOCK_REC_NOT_GAP|LOCK_X,获取成功记录就是 d_id='1' b_id='1' is_dropped=0这条数据。S1(select操作)
回表获取全部数据,这个时候需要主键上的相应的行锁。LOCK_REC_NOT_GAP|LOCK_X获取成功S2(select操作)
通过唯一性索引定位索引数据试图获取了唯一索引DealerAndBrokerAndDropped 上的
LOCK_REC_NOT_GAP|LOCK_X,获取失败记录就是 d_id='1' b_id='1' is_dropped=0这条数据,处于等待状态。S1(update操作)
通过索引DealerAndBrokerAndDropped 查找数据(注意这里已经不是唯一性定位操作了,下面会做分析),这个时候首先需要通过查询条件获取出需要更新的第一条数据,实际上这个时候也是d_id='1' b_id='1' is_dropped=0这条数据,需要获取的锁为LOCK_ORDINARY[next_key_lock]|LOCK_X,这个时候我发现虽然S1之前获取了这条数据的锁,但是锁模式变化了(一致不会重新获取,下面会分析这种行为),因此这里需要重新获取,但是这显然是不行的,因为S2都还处于等待中,因此这里也发生了等待。因此通过这个过程就出现死锁,S2等S1 S1等S2。

 

这是本死锁的一个最重要原因,知道了这个原因这个案例就理解了。首先我们先看这个update语句:

update t8 set u_c='b' where d_id='1' and b_id='1';

我们发现这个时候唯一索引还少一个条件也就是is_dropped字段,这个时候本次定位查询不会判定为唯一性查询,而是普通的二级索引定位方式,这个时候RR模式出现LOCK_ORDINARY[next_key_lock]就显得很自然了,下面是这个判断过程,代码位于row_search_mvcc中。

稍微解释一下,唯一性查找条件至少包含如下3点:

  • 1. 索引具有唯一性

  • 2. 查询的字段数量和索引唯一性字段数量相同

  • 3. 是主键或者查询条件中不包含NULL值

注意第3点源码说明如下:

 

满足上面4点条件才能确认为唯一查找,本查询由于第3条不满足因此,因此判定失败。
不仅如此如果本条数据加锁成功,那么你会看到如下的结果:      ;;

我们发现DealerAndBrokerAndDropped唯一索引的下一条记录也加了gap lock,这完全是RR模式非唯一索引的加锁行为。