MySQL实战45讲——40insert语句的锁为什么这么多?

目录

insert …select 语句(唯一键冲突加next-key lock(读锁))

insert 循环写入

insert into …on duplicate key update(主键冲突加next-key lock(写锁))

小结

上期问题时间


本文摘抄自林晓斌老师《MySQL实战45讲》,文中有部分自己的理解和分析。在上一篇文章中,我提到 MySQL 对自增主键锁做了优化,尽量在申请到自增 id 以后,就释放自增锁。

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

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

insert …select 语句(唯一键冲突加next-key lock(读锁)

我们先从昨天的问题说起吧。表 t 和 t2 的表结构、初始化数据语句如下,今天的例子我们还是针对这两个表展开。

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 时执行:

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

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

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

img

图 1 并发 insert 场景

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

但如果没有锁的话,就可能出现 session 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 的时候,对目标表也不是锁全表,而是只锁住需要访问的资源。

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

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

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

这个语句的加锁范围,就是表 t 索引 c 上的 (3,4] 和 (4,supremum] 这两个 next-key lock,以及主键索引上 id=4 这一行。

它的执行流程也比较简单,从表 t 中按照索引 c 倒序,扫描第一行,拿到结果写入到表 t2 中。

因此整条语句的扫描行数是 1。

这个语句执行的慢查询日志(slow log),如下图所示:

img

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

通过这个慢查询日志,我们看到 Rows_examined=1,正好验证了执行这条语句的扫描行数为 1。

那么,如果我们是要把这样的一行数据插入到表 t 中的话:

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

语句的执行流程是怎样的?扫描行数又是多少呢?

这时候,我们再看慢查询日志就会发现不对了。

img

图 3 慢查询日志 – 将数据插入表 t

可以看到,这时候的 Rows_examined 的值是 5。

我在前面的文章中提到过,希望你都能够学会用 explain 的结果来"脑补"整条语句的执行过程。今天,我们就来一起试试。

如图 4 所示就是这条语句的 explain 结果。

img

图 4 explain 结果

从 Extra 字段可以看到"Using temporary"字样,表示这个语句用到了临时表。也就是说,执行过程中,需要把表 t 的内容读出来,写入临时表。

图中 rows 显示的是 1,我们不妨先对这个语句的执行流程做一个猜测:如果说是把子查询的结果读出来(扫描 1 行),写入临时表,然后再从临时表读出来(扫描 1 行),写回表 t 中。那么,这个语句的扫描行数就应该是 2,而不是 5。

所以,这个猜测不对。实际上,Explain 结果里的 rows=1 是因为受到了 limit 1 的影响。

从另一个角度考虑的话,我们可以看看 InnoDB 扫描了多少行。如图 5 所示,是在执行这个语句前后查看 Innodb_rows_read 的结果。

img

图 5 查看 Innodb_rows_read 变化

可以看到,这个语句执行前后,Innodb_rows_read 的值增加了 4。因为默认临时表是使用 Memory 引擎的,所以这 4 行查的都是表 t,也就是说对表 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。

也就是说,这个语句会导致在表 t 上做全表扫描,并且会给索引 c 上的所有间隙都加上共享的 next-key lock。所以,这个语句执行期间,其他事务不能在这个表上插入数据。

至于这个语句的执行为什么需要临时表,原因是这类一边遍历数据,一边更新数据的情况,如果读出来的数据直接写回原表,就可能在遍历过程中,读到刚刚插入的记录,新插入的记录如果参与计算逻辑,就跟语义不符。

由于实现上这个语句没有在子查询中就直接使用 limit 1,从而导致了这个语句的执行需要遍历整个表 t。它的优化方法也比较简单,就是用前面介绍的方法,先 insert into 到临时表 temp_t,这样就只需要扫描一行;然后再从表 temp_t 里面取出这行数据插入表 t1。

当然,由于这个语句涉及的数据量很小,你可以考虑使用内存临时表来做这个优化。使用内存临时表优化时,语句序列的写法如下:

create temporary table temp_t(c int,d int) engine=memory;
insert into temp_t  (select c+1, d from t force index(c) order by c desc limit 1);
insert into t select * from temp_t;
drop table temp_t;

前面的两个例子是使用 insert … select 的情况,接下来我要介绍的这个例子就是最常见的 insert 语句出现唯一键冲突的情况。

对于有唯一键的表,插入数据时出现唯一键冲突也是常见的情况了。我先给你举一个简单的唯一键冲突的例子。

img

图 6 唯一键冲突加锁

这个例子也是在可重复读(repeatable read)隔离级别下执行的。可以看到,session B 要执行的 insert 语句进入了锁等待状态。

也就是说session A 执行的 insert 语句,发生唯一键冲突的时候,并不只是简单地报错返回,还在冲突的索引上加了锁。我们前面说过,一个 next-key lock 就是由它右边界的值定义的。这时候,session A 持有索引 c 上的 (5,10] 共享 next-key lock(读锁)。

至于为什么要加这个读锁,其实我也没有找到合理的解释。从作用上来看,这样做可以避免这一行被别的事务删掉。

这里官方文档有一个描述错误,认为如果冲突的是主键索引,就加记录锁,唯一索引才加 next-key lock。但实际上,这两类索引冲突加的都是 next-key lock。

备注:这个 bug,是我在写这篇文章查阅文档时发现的,已经发给官方并被 verified 了。

有同学在前面文章的评论区问到,在有多个唯一索引的表中并发插入数据时,会出现死锁。但是,由于他没有提供复现方法或者现场,我也无法做分析。所以,我建议你在评论区发问题的时候,尽量同时附上复现方法,或者现场信息,这样我才好和你一起分析问题。

这里,我就先和你分享一个经典的死锁场景,如果你还遇到过其他唯一键冲突导致的死锁场景,也欢迎给我留言。

图 7 唯一键冲突 – 死锁

T2时刻的加锁信息分析

SELECT a.TRX_ID '事务ID',a.trx_state '事务状态',b.lock_id '锁ID' ,b.lock_mode '锁模式' ,b.lock_type '锁类型'
FROM information_schema.INNODB_TRX a,information_schema.innodb_locks b 
WHERE  a.TRX_ID =b.lock_TRX_ID

 

-- INNODB_LOCK_WAITS记录了事务需要请求的锁,请求过程中因为谁持有的另外的锁阻塞了导致它拿不到想要的锁而等待
SELECT information_schema.INNODB_LOCK_WAITS.`requesting_trx_id` '请求锁的事务ID', 
information_schema.INNODB_LOCK_WAITS.`requested_lock_id` '请求锁的锁ID', 
information_schema.INNODB_LOCK_WAITS.`blocking_trx_id` '造成阻塞的事务ID', 
information_schema.INNODB_LOCK_WAITS.`blocking_lock_id` '造成阻塞的锁ID'
FROM information_schema.INNODB_LOCK_WAITS 

---TRANSACTION 485066, ACTIVE 50 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 36, OS thread handle 2192, query id 9055 localhost ::1 root starting
SHOW ENGINE INNODB STATUS
TABLE LOCK table `test`.`t` trx id 485066 lock mode IX
RECORD LOCKS space id 1074 page no 4 n bits 72 index c of table `test`.`t` trx id 485066 lock_mode X locks rec but not gap
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000005; asc     ;;
 1: len 4; hex 80000008; asc     ;;
 
 ---TRANSACTION 485067, ACTIVE 44 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 38, OS thread handle 7724, query id 9028 localhost ::1 root update
INSERT INTO t VALUES(NULL, 5,5)
------- TRX HAS BEEN WAITING 44 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1074 page no 4 n bits 72 index c of table `test`.`t` trx id 485067 lock mode S waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000005; asc     ;;
 1: len 4; hex 80000008; asc     ;;
 
 
 ---TRANSACTION 485068, ACTIVE 35 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 39, OS thread handle 3272, query id 9034 localhost ::1 root update
INSERT INTO t VALUES(NULL, 5,5)
------- TRX HAS BEEN WAITING 35 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1074 page no 4 n bits 72 index c of table `test`.`t` trx id 485068 lock mode S waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000005; asc     ;;
 1: len 4; hex 80000008; asc     ;;

sessionA持有c=5的X型记录锁,sessionB和sessionC申请持有c=5的S型记录锁,sessionB和sessionC都被X型记录锁阻塞还未申请到S锁(先开启的事务id比后开启的事务id小,485066是sessionA,485067是sessionB,485068是sessionC;关于INNODB_LOCKS 表作用官方解释:看官方解释该表记录了事务已经申请但还没持有的锁sessionB和sessionC申请持有S型记录锁),还有事务已经持有但阻塞了其他事务获取锁的锁sessionA持有X型记录锁):所以林老师在下面提到的产生死锁的逻辑说T2时刻,sessionB和sessionC加了读锁是有问题的,因为读写锁冲突,是拿不到读锁的,上面日志也显示是lock mode S waiting。

  • lock_mode(锁模式):锁的模式。有如下锁类型:行级锁包括:S、X、IS、IX,分别代表:共享锁、排它锁、意向共享锁、意向排它锁。表级锁包括:S_GAP、X_GAP、IS_GAP、IX_GAP 和 AUTO_INC,分别代表共享间隙锁、排它间隙锁、意向共享间隙锁、意向排它间隙锁和自动递增锁。
  • lock_type(锁类型):RECORD 代表行级锁,TABLE 代表表级锁。

T3时刻的加锁信息分析(SHOW ENGINE INNODB STATUS:在 session A 执行 rollback 语句回滚的时候,session C 几乎同时发现死锁并返回。


可查看第30篇的死锁信息分析,因为超时,我这里新建了事务操作,484882是sessionB,484883是sessionC
------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-03-06 14:22:46 0x890
*** (1) TRANSACTION: (sessionB)
TRANSACTION 484882, ACTIVE 33 sec inserting


*** mysql tables in use 1说明当前的事务使用一个表,locked 1表示表上有一个表锁
mysql tables in use 1, locked 1

*** LOCK WAIT表示正在等待锁,2 row lock(s) 说明sessionB有两把行锁(包括行锁的三种类型)
*** 这里没有给出sessionB持有哪些锁,是sessionC加锁情况推断它也加了(4,supremum]这个临键范围的读锁
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 20, OS thread handle 7052, query id 5269 localhost ::1 root update
INSERT INTO t VALUES(NULL, 5,5)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: (sessionB在等待X锁:(4,supremum]这个临键锁)
RECORD LOCKS space id 1071 page no 4 n bits 80 index c of table `test`.`t` trx id 484882 lock_mode X insert intention waiting
*** 记录锁加在上限值supremum上,而加锁单位是临键锁,所以其实就是加了(4,supremum]
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION: (sessionC)
TRANSACTION 484883, ACTIVE 27 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 21, OS thread handle 2192, query id 5275 localhost ::1 root update
INSERT INTO t VALUES(NULL, 5,5)
*** (2) HOLDS THE LOCK(S):(sessionC持有c索引上的s锁,(4,supremum]这个临键范围的读锁)
RECORD LOCKS space id 1071 page no 4 n bits 80 index c of table `test`.`t` trx id 484883 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BEGRANTED:(sessionC在等待X锁:(4,supremum]这个临键锁)
RECORD LOCKS space id 1071 page no 4 n bits 80 index c of table `test`.`t` trx id 484883 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS

上面日志得出sessionB和sessionC对c=supremum加了s锁临键锁,它们都在等c=supremum X锁临键锁;

这里已经不是对c=5加s锁,明显回滚前后申请的锁信息不一样了。

  • 记录锁(LOCK_REC_NOT_GAP): lock_mode X locks rec but not gap
  • 间隙锁(LOCK_GAP): lock_mode X locks gap before rec
  • Next-key 锁(LOCK_ORNIDARY): lock_mode X(默认加锁)
  • 插入意向锁(LOCK_INSERT_INTENTION): lock_mode X locks gap before rec insert intention

这个死锁产生的逻辑是这样的:

  1. 在 T1 时刻,启动 session A,并执行 insert 语句,此时在索引 c 的 c=5 上加了记录锁。注意,这个索引是唯一索引,因此退化为记录锁(如果你的印象模糊了,可以回顾下[第 21 篇文章]介绍的加锁规则。但这里的问题是c=5记录不存在,我们看另一篇更详细的MySQL行锁加锁规则之等值查询,这是唯一索引等值不匹配,所以是加supremum的间隙锁,即(4,supremum)才对,不过查看SHOW ENGINE INNODB STATUS发现这里加了0把锁,意思是T1时刻还没锁竞争,压根不需要加锁,所以这里的结论也有问题。这里也引出加锁时机的问题,通过实操发现就算同时显示开启三个事务然后执行T1也是不加锁的,其他事务的sql跟已经开启的事务可能冲突才加锁?具体实现是怎么样呢?)。
  2. 在 T2 时刻,session B 要执行相同的 insert 语句,发现了唯一键冲突,加上读锁;同样地,session C 也在索引 c 上,c=5 这一个记录上,加了读锁(注意同一记录的读写锁是冲突的,所以这里应该只是申请加读锁,还未加上,T3时刻session A回滚释放记录锁,读锁才加上。有人也提出了同样的疑问,林老师在下篇文章解答了这个问题如果 sessionA 拿到 c=5 的记录锁是写锁,那为什么 sessionB 和 sessionC 还能加 c=5 的读锁呢?):“next-key lock 是先加间隙锁,再加记录锁的。加间隙锁成功了,加记录锁就会被堵住。),这句话的意思是next-key lock =记录锁+间隙锁,第一步是加间隙锁,间隙锁与间隙锁不冲突,所以间隙锁加读写锁都可以,然后再加记录锁,因为记录锁与记录锁冲突被堵住,但这也只是说明间隙锁与间隙锁不冲突,记录锁和记录锁还是冲突的,也就是sessionA 记录锁加了写锁,sessionB 和 sessionC 不能加 c=5 的读锁,所以个人认为林老师这里是表达有问题,同时我们在上面分析T2时刻的日志时发现sessionB和sessionC只是在等待读锁还未持有,关于临键锁加锁过程具体可参考:第21篇加锁规则:为什么我只改一行的语句,锁这么多?这篇文章最后一个关于死锁的例子)
  3. T3 时刻,session A 回滚。这时候,session B 和 session C 都试图继续执行插入操作,都要加上写锁。两个 session 都要等待对方的行锁,所以就出现了死锁。(这时候变成了对c=supremum加了读锁,申请c=supremum写锁,跟上面的c=5没关系了,这里的疑问可看这篇文章:唯一索引冲突,为什么主键的 supremum 记录会加 next-key 锁?

林老师提到的用动态的观点看加锁里面的死锁例子(c是普通索引)

  1. select id from t where c in(5,20,10) lock in share mode;

  2. select id from t where c in(5,20,10) order by c desc for update;

间隙锁与间隙锁不冲突,可以叠加,读写锁才冲突,间隙锁其实是配合读写锁用的 lock in share mode其实是给间隙加读锁,for update给间隙加写锁。(这里理解是错的,间隙锁不冲突就是(n,m)这个间隙多个事务都可以同时加读写锁,读写锁是锁的属性,需要跟其他锁搭配使用,并不是单纯的读写就冲突,而要看读写锁和哪个锁搭配,比如搭配记录锁那读写就冲突,搭配间隙锁那读写就不冲突,这里没改掉是为了保留我对锁理解模糊不清的一个历程)

这里是经典的死锁循环等待:语句1拿了c=5记录锁等c=10记录锁,语句2拿了c=10记录锁等c=5记录锁。记录锁与记录锁互斥,记录锁与临键锁互斥,临键锁与临键锁互斥,由于临键锁=记录锁+间隙锁,而间隙锁与间隙锁不互斥,间隙锁与记录锁也不互斥,所以这里归根到底就是记录锁与记录锁互斥。

发现了一个有趣的现象:select id from t where c in(5,20,10) order by c desc for update;把order by c desc去掉,升序加了9把锁,降序加了8把锁,少了一把(5,10)的间隙锁,不知道为何。

而把条件改成in(5,10,15),升序依然9把锁,而降序则加了7把锁,少了(5,10)和(10,15)两把间隙锁,因为(5,10]和(10,15]其实已经包含了(5,10)和(10,15)所以这是降序的一种优化?

这个流程的状态变化图如下所示。

图 8 状态变化图 – 死锁

我们一般理解的死锁:

上面涉及的死锁:

疑问,为啥加锁顺序一致,还是死锁了呢?这是因为A这个锁的性质不一样,按一般理解的本来死锁两个线程都按AB顺序加锁,如果线程1拿到A锁,线程2去拿A锁是互斥(A是写锁)的,得阻塞等待所以不会陷入循环等待,而这个案例里的A是读锁,顺序一样因为不互斥所以两个事务都能持有。

那疑问又来了,如果去掉sessionA,只执行sessionB和sessionC却又不会死锁,sessionC会报错Lock wait timeout exceeded; try restarting transaction,看加锁信息,是sessionB加了X锁,而sessionC申请加S锁,由于等待sessionB释放X锁而阻塞,并没陷入循环等待。

另外如果sessionA是提交而不是回滚,为啥sessionB和sessionC都直接报 Duplicate entry '5' for key 'c'而没有陷入死锁呢,这里就很困惑了,sessionB和sessionC都在申请s锁,没死锁就说明sessionB和sessionC至少有一个放弃申请读锁直接申请到写锁了,以下是sessionA正常提交的锁信息, 484991,484992,484993为sessionA,sessionB,sessionC事务id,很明显sessionB和sessionC都只有一个写锁(因为报写冲突挂了肯定是写锁了),那就是读锁都放弃了,具体Mysql底层是什么一个情况呢?

LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 282884906731312, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 282884906736544, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 282884906735672, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 282884906734800, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 282884906733056, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 484993, ACTIVE 57 sec   (1 row lock(s)表示加了一把锁,这个可以通过一个事务多个加锁情况验证,而且是包括读锁的)
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 32, OS thread handle 7628, query id 6270 localhost ::1 root
---TRANSACTION 484992, ACTIVE 68 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 20, OS thread handle 7052, query id 6302 localhost ::1 root starting
show engine innodb status

insert into …on duplicate key update(主键冲突加next-key lock(写锁)

上面这个例子是主键冲突后直接报错,如果是改写成

insert into t values(11,10,10) on duplicate key update d=100; 

的话,就会给索引 c 上 (5,10] 加一个排他的 next-key lock(写锁)。 insert into … on duplicate key update 这个语义的逻辑是,插入一行数据,如果碰到唯一键约束,就执行后面的更新语句。*

注意,如果有多个列违反了唯一性约束,就会按照索引的顺序,修改跟第一个索引冲突的行

现在表 t 里面已经有了 (1,1,1) 和 (2,2,2) 这两行,我们再来看看下面这个语句执行的效果:

img

图 9 两个唯一键同时冲突

可以看到,主键 id 是先判断的,MySQL 认为这个语句跟 id=2 这一行冲突,所以修改的是 id=2 的行。

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

小结

  1. insert …select 语句唯一键冲突加next-key lock(读锁)
  2. insert into …values 语句唯一键冲突加next-key lock(读锁)
  3. insert into …on duplicate key update(主键冲突加next-key lock(写锁)

今天这篇文章,我和你介绍了几种特殊情况下的 insert 语句。 insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。

而如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。 insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。

最后,我给你留一个问题吧。

你平时在两个表之间拷贝数据用的是什么方法,有什么注意事项吗?在你的应用场景里,这个方法,相较于其他方法的优势是什么呢?

你可以把你的经验和分析写在评论区,我会在下一篇文章的末尾选取有趣的评论来和你一起分析。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。

上期问题时间

我们已经在文章中回答了上期问题。

有同学提到,如果在 insert … select 执行期间有其他线程操作原表,会导致逻辑错误。其实,这是不会的,如果不加锁,就是快照读。

一条语句执行期间,它的一致性视图是不会修改的,所以即使有其他事务修改了原表的数据,也不会影响这条语句看到的数据。

  • 12
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值