0%

insert语句的锁为什么这么多?

在上一篇文章中,我提到MySQL对自增主键锁做了优化,尽量在申请到自增id以后,就释放自增锁。

因此,insert语句是一个很轻量的操作。不过,这个结论是对于“普通的insert语句”才有效。也就是说,还有些insert语句是属于“特殊情况”的,在执行过程中需要给其他资源加锁,或者无法在申请到自增id以后就立马释放自增锁。

那么,今天这篇文章,我们就一起来聊聊这个话题。

1. insert...select语句

我们先从上一篇文章的话题说起吧。表t1和t2的表结构、初始化数据语句如下,今天的例子我们还是针对这两个表展开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `t` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);

create table t2 like t

现在,我们一起来看看为什么在可重复读隔离级别下,binlog_format=statement时执行:

1
insert iinto t2(c, d) select c, d from t;

这个语句时需要对表t的所有行和间隙加锁呢?

其实,这个问题我们需要考虑的还是日志和数据的一致性。我们看下这个执行序列:

实际的执行效果是,如果session B先执行,由于这个语句对表t主键索引加了(-∞,1]这个next-key lock,会在语句执行完成后,才允许session A的insert语句执行。

但如果没有锁的话,就可能出现session B的insert语句先执行,但是后写入binlog的情况。于是,在binlog_format=statement的情况下,binlog里面就记录了这样的语句序列:

1
2
insert into t values(-1, -1, -1);
insert into t2(c, d) select c, d from t;

这个语句到了备库执行,就会把id=-1这一行也写到表t2中,出现主备不一致。

2. insert循环写入

当然了,之心吧insert...select的时候,对目标表也不是锁全表,而是只锁住需要访问的资源。

如果现在有这么一个需求:要往表t2中插入一行数据,这一行的c值是表t中c值的最大值加1。

此时,我们可以这么写这条SQL语句:

1
insert into