insert…select为什么要对目标表加锁
测试语句如下,注:隔离模式是RR,binlog_format=statement
1 | CREATE TABLE `t` ( |
执行如下的语句:
1 | insert into t2(c,d) select c,d from t; |
这条语句是给表t所有的字段和间隙加锁,原因是考虑到数据的一致性例如下面的情况:
SessionA和SessionB同时执行,SessionB会给索引c的(-supernum,-1]加上nextkey-lock,原因是如果没有这个锁,很可能主库上B先执行,A后执行,但是binlog上回是A先执行,B后执行,导致从库会出现-1的值,造成主备不一致
当然了,目标表的锁其实是给涉及到的字段加锁,详见前面的加锁原理
见下面俩种情况:
1 | insert into t2(c,d) select c+1,d from t order by c desc limit 1; |
这条语句实际上会对表t中的索引c(3,4]和(4,supernum)加锁。扫描行数只有2行
但是下面的语句就不是很好了,把表t最大值+1插入会原表
1 | insert into t(c,d) select c+1,d from t order by c desc limit 1; |
原因是:
- 在一张表中操作,mysql为了select时候读到insert的值,需要将select的结果导入到临时表中
- 临时表是memory类型的表,因为有limit 1,需要将表里的数据都导入到临时表中,排序取第一条
- 所以需要全表扫描,并且对全表所有的行加锁。
优化的方法是:采用临时表来解决语句如下
1 | create temporary table temp_t(c int,d int) engine=memory; |
insert违反唯一约束加锁
见下图:
SessionA在第2次插入时候违反了唯一约束,这时候SessionB的语句并没有因为SessionA的insert失败开始执行反而是block住。原因是SessionA在后滚之前为冲突的索引(5,10]加了nextkey-lock读锁。
在并发情况下也会引起死锁,如图:
原因是:
- SessaionA为记录加行锁c=5。(唯一索引等值查询是行锁)
- T2时刻SessionB和SessionC都对c=5加了读锁。(违反唯一约束)这时候都是等待状态
- T3时刻SessionArollback,
- SessionB和SessionCD的读锁生效;
- SessionB和SessionC要继续执行插入操作,这时候要对数据加写锁,这时候需要等待对方释放读锁,循环等待发生死锁。
见图:
解决方法
- 事务中唯一约束冲突后要尽快提交或者回滚,防止并发写循环等待;
- 采用insert into…duplicate key update的方式,因为这时候会加一个nextkey-lock写锁是互斥的。这样sessionB和sessionC不会循环等待。