40 | insert语句的锁为什么这么多?(insert为什么加读锁?)

尽量申请自增 id 后,释放自增锁(上一篇)。

insert 轻量对普通才有效。有些 insert :其他资源加锁,申请自增 id 后,不能马上释放。

一、insert … select 语句

t 和 t2 的表结构、初始化数据

可重复读隔离级别binlog_format=statementinsert into  t2(c,d) select c,d from t;

所有行和间隙加锁,保证日志和数据一致性。执行序列:

图 1 并发 insert 场景

B先执行, t 主键索引加了 (-∞,1] 这个 next-key lock,执行完A 的 insert 执行。

没有锁,B 的 insert先执行,后写入 binlog。binlog_format=statement ,binlog 里面就记录了这样的语句序列:

insert into t  values(-1,-1,-1);

insert into  t2(c,d) select c,d from t;

到了备库执行,把 id=-1 写到表 t2 中,主备不一致。

二、insert 循环写入

insert …select 只锁需访问资源,不是锁全表。

2.1 insert into t2 扫描行数Rows_examined=1,受 limit 1 影响

insert into  t2(c,d)  (select c+1, d from t force  index(c) order by c desc limit 1);   //插入一行数据, c 值= c 最大值+ 1。

加锁范围: 索引 c 上 (3,4] (4,supremum]这两个 next-key lock,主键索引 id=4 这行。

索引 c 倒序,扫描第一行,结果写入 t2 。

图 2 慢查询日志 -- 将数据插入表 t2

2.2 插入表 tRows_examined = 5  ,用临时表

insert into  t(c,d)  (select c+1, d from t force  index(c) order by c desc limit 1); 

图 3 慢查询日志 -- 将数据插入表 t  
图 4 explain 结果(  explain “脑补”执行过程。 )

Using temporary用临时表:t 内容读出,写入临时表。

rows =1,猜测:子查询结果读出来(扫描 1 行),写入临时表,从临时表读出来(扫描 1 行),写回表 t 中。扫描行数 2(不是5),猜测不对

图 5 查看 Innodb_rows_read 变化

2.3 用临时表原因

边遍历边更新,读出直接写回原表,遍历过程,读到刚插记录(如果参与计算逻辑,跟语义不符)。

Innodb_rows_read增加 4。 Memory 全表扫描表 t

1. 创建临时表,字段 c 和 d

2.  索引 c 扫描表 t,依次取 c=4、3、2、1回表,读到 c 和 d 写入临时表。Rows_examined=4

3.   limit 1,只取临时表第一行,插入 t 。Rows_examined加 1,= 5。

索引 c 上间隙都加上共享 next-key lock。其他事务不能insert

2.4 优化办法

1:没在子查询中直接使用 limit 1遍历整表 。应先 insert into临时表 temp_t,只需要扫描一行;取出插入t1

2:数据量用内存临时表

三、insert 唯一键冲突

图 6 唯一键冲突加锁

可重复读(repeatable read)。 B insert 锁等待。

A 唯一键冲突,冲突索引上加锁。 next-key lock 由右边界值定义。 A 持有索引 c 上 (5,10] 共享 next-key lock(读锁)。

主键、唯一索引冲突加都 next-key lock。加读锁,避免这行被别的事务删掉。

3.1 经典死锁场景

图 7 唯一键冲突 -- 死锁  

session A rollback 时,C 几乎同时发现死锁并返回。

1.  T1 ,启动  A,insert 语句,索引c=5记录锁。c是唯一索引,退化为记录锁(如果你的印象模糊了,可以回顾下第 21 篇文章介绍的加锁规则)。

2.  T2 , B 相同 insert 语句,唯一键冲突加读锁?; C 也在索引c 上,c=5 记录上加读锁

3.  T3 时刻,A 回滚。 B 和 C 执行插入操作,加写锁。等待对方行锁,死锁

图 8 状态变化图 -- 死锁  

四、insert into … on duplicate key update

主键冲突后直接报错,如改写成

insert into t  values(11,10,10) on duplicate key update d=100;  给索引 c 上 (5,10] 加排他 next-key lock(写锁)

插入碰到唯一键约束,执行后面更新语句。

多个列违反了唯一性约束,按照索引的顺序,修改跟第一个索引冲突的行。

已有 (1,1,1) 和 (2,2,2)

图 9 两个唯一键同时冲突

先判断主键 id 是的,跟 id=2 这一行冲突,修改 id=2 行。

需要注意的是,执行这条语句affected rows 返回的是 2,很容易造成误解。真正更新的只有一行,insert 和 update 都认为自己成功了,计数都加1

小结

insert … select 拷贝数据。可重复读, select 扫描记录的间隙加读锁

insert 和 select 对象是同一个表循环写入。引入用户临时表优化

insert 唯一键冲突,冲突唯一值上加共享 next-key lock(S 锁)尽快提交或回滚事务,避免加锁时间长

问题

两个表之间拷贝数据什么方法,注意事项?优势?

下一篇文章。

你可能感兴趣的:(40 | insert语句的锁为什么这么多?(insert为什么加读锁?))