引言
在分析了 update 与 delete 语句的加锁流程后,本文分析 insert 语句的加锁流程,主要分为以下两种场景:
-
待插入记录的下一条记录上已经被其他事务加了间隙锁时 -
遇到冲突键时
每种场景又可以细分为三种场景,由于篇幅原因,全文拆分为两篇文章,本文是第一篇文章,分析第一种场景,也就是不考虑唯一索引的场景。
由于个人能力有限,以下内容可能有误,欢迎批评指正。
在此之前建议阅读之前的两篇文章:
准备工作
测试数据
数据库版本:5.7.24
事务隔离级别:RR
测试数据
mysql> show create table t_lock G
*************************** 1. row ***************************
Table: tt
Create Table: CREATE TABLE `t_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT '0',
`b` int(11) DEFAULT '0',
`c` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_a` (`a`),
KEY `idx_b` (`b`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> insert into t_lock(id, a, b, c) values(1, 1, 1, 1),(5, 5, 5, 5),(9, 9, 9, 9);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
其中:
-
测试表中有三个索引,包括主键索引、二级唯一索引、二级非唯一索引。
查看数据
mysql> select * from t_lock;
+----+------+------+------+
| id | a | b | c |
+----+------+------+------+
| 1 | 1 | 1 | 1 |
| 5 | 5 | 5 | 5 |
| 9 | 9 | 9 | 9 |
+----+------+------+------+
3 rows in set (0.00 sec)
现象
首先测试最简单的场景,只有一个事务,执行 insert 插入成功。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t_lock(id,a,b,c) values(6,6,6,6);
Query OK, 1 row affected (0.00 sec)
查看事务信息,显示没有锁。
---TRANSACTION 136354, ACTIVE 13 sec
1 lock struct(s), heap size 1136, 0 row lock(s), undo log entries 1
MySQL thread id 81, OS thread handle 139726190700288, query id 17622 127.0.0.1 admin
TABLE LOCK table `test_zk`.`t_lock` trx id 136354 lock mode IX
那么,insert 执行时会加锁吗?
显然,insert 会加锁,原因是从死锁日志中可以发现很多死锁案例都是 insert 触发,其中 insert 最常见的锁等待场景是插入意向锁等待。
本文将测试并分析在什么场景下 insert 需要加锁。
场景
insert 语句的执行流程可以参考文章 MySQL · 源码分析 · 一条insert语句的执行过程,本文主要关注 insert 语句的加锁流程。
参考小孩子4919 大佬的文章 两条一样的INSERT语句竟然引发了死锁?,insert 语句正常情况下使用隐式锁,因此不会创建锁结构,但是特殊场景下需要加锁,具体包括以下三种场景:
-
待插入记录的下一条记录上已经被其他事务加了间隙锁时,发生插入意向锁等待; -
遇到冲突键时,加锁类型与具体场景有关; -
外键检查时,由于不推荐使用外键,因此本文不做介绍。
其中【遇到冲突键】的场景根据冲突数据的状态又可以分为以下三种:
-
与已有数据冲突; -
已未提及事务冲突,也就是多个事务同时 insert 相同的记录; -
与已删除未 purge 数据冲突。
显然,如果表中没有唯一键,加锁场景只有一种,就是存在间隙锁导致插入意向锁等待。
下面对比测试 insert 语句在四种场景下加锁的差异。
-
下一条记录上有间隙锁 -
与已有数据冲突 -
已未提及事务冲突 -
与已删除未 purge 数据冲突
其中调试过程中在lock_rec_lock
函数设置断点。
由于篇幅原因,全文拆分为两篇文章,本文是第一篇文章,分析第一种场景,也就是不考虑唯一索引的场景。
下一条记录上有间隙锁
场景 1
测试
操作流程
time | session 1 | session 2 |
---|---|---|
1 | begin; update t_lock set c=3 where b=3; |
|
2 | begin; insert into t_lock(id,a,b,c) values(3,3,3,3); blocking |
|
3 | rollback; |
其中:
-
由于 update/delete 不存在的记录时加间隙锁,因此先 update 后 insert; -
update 语句的查询条件是二级非唯一键的等值查询; -
最后事务 1 回滚时释放间隙锁。
时刻 1 事务信息
---TRANSACTION 136382, ACTIVE 3 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 86, OS thread handle 139726189901568, query id 18244 127.0.0.1 admin
TABLE LOCK table `test_zk`.`t_lock` trx id 136382 lock mode IX
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136382 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
其中:
-
事务 1 更新不存在的记录时持锁 gap lock b = 5,原因是索引等值查询遍历到第一个不满足条件的记录时,next-key lock 退化为间隙锁。
时刻 2 事务信息
---TRANSACTION 136383, ACTIVE 2 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 87, OS thread handle 139726190434048, query id 18247 127.0.0.1 admin update
insert into t_lock(id,a,b,c) values(3,3,3,3)
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136383 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
------------------
TABLE LOCK table `test_zk`.`t_lock` trx id 136383 lock mode IX
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136383 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
其中:
-
事务 2 发生插入意向锁等待,原因是插入意向锁与间隙锁冲突。
时刻 3 事务信息
---TRANSACTION 136383, ACTIVE 47 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 87, OS thread handle 139726190434048, query id 18247 127.0.0.1 admin
TABLE LOCK table `test_zk`.`t_lock` trx id 136383 lock mode IX
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136383 lock_mode X locks gap before rec insert intention
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
其中:
-
insert 在插入意向锁等待后持有插入意向锁,对比没有锁等待时不会持有该锁。
堆栈信息
给lock_rec_lock
函数设置断点后 insert 发生锁等待直到超时,显示没有触发断点,表明插入意向锁等待的实现并没有调用该函数。
为了获取堆栈信息分析插入操作的详细执行流程,删除lock_rec_lock
函数断点,新增以下两个断点:
-
lock_rec_insert_check_and_lock
函数,用于插入操作中检查是否发生插入意向锁等待; -
add_to_waitq
函数,用于在发生锁等待时进行加锁,其中进行死锁检测。
重新执行插入操作时触发断点,堆栈信息如下所示。
首先插入主键前检查是否发生插入意向锁等待,判断不需要等待,因此主键索引插入成功。

然后插入二级唯一索引前检查是否发生插入意向锁等待,判断不需要等待,因此二级唯一索引插入成功。

最后插入二级非唯一索引前检查是否发生插入意向锁等待,判断需要等待,因此二级非唯一索引插入失败。

由于事务 1 的更新条件是索引 idx_b,因此事务 2 插入索引 idx_b 时发生插入意向锁等待。

分析
lock_rec_insert_check_and_lock
函数中判断是否发生插入意向锁等待,主体代码如下所示。
// 等待中的锁也会阻塞
/* If another transaction has an explicit lock request which locks
the gap, waiting or granted, on the successor, the insert has to wait. */
// 下一条记录上有锁,然后判断与其他事务是否有锁冲突
// 插入意向锁
const ulint type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION;
// 判断是否与插入意向锁冲突,也就是检查是否有间隙锁
const lock_t* wait_for = lock_rec_other_has_conflicting(
type_mode, block, heap_no, trx);
// 有冲突时进入锁等待
if (wait_for != NULL) {
// RecLock 是一个辅助结构,主要是线程结构、事务结构、锁模式、索引名称、page no、heap_no 作为构造参数。
RecLock rec_lock(thr, index, block, heap_no, type_mode);
// 如果与插入意向锁有冲突,创建一个插入意向锁,加到事务锁列表中去,插入等待队列中
err = rec_lock.add_to_waitq(wait_for);
} else {
// 使用隐式锁
err = DB_SUCCESS;
}
其中:
-
判断要插入位置的下一行记录上是否有锁,如果有锁,判断是否与插入意向锁冲突; -
如果冲突,发生锁等待,具体是插入意向锁等待,否则加锁成功,具体是使用隐式锁。
事务 2 当前处于锁等待状态,最终有可能执行成功或失败:
-
如果事务 1 提交或回滚后释放锁,事务 2 将继续执行,也就是插入二级非唯一索引; -
如果在此之前事务 2 锁等待超时,事务 2 将回滚,也就是删除已经插入的主键索引与二级唯一索引。
因此给lock_rec_inherit_to_gap
函数设置断点,该函数用于删除操作过程中的锁继承。
在锁等待超时后,堆栈信息如下所示。

其中:
-
lock_rec_inherit_to_gap
函数入参heir_heap_no=receiver_heap_no=3, heap_no=donator_heap_no=5
,用于判断是否需要将 新插入行上的锁转移到下一行,也就是表中第二行记录; -
判断不需要发生锁继承,原因是新插入行上没有显式锁,表明删除主键索引时没有将隐式锁转换成显式锁; -
主键索引标记删除函数 row_upd_del_mark_clust_rec
中使用隐式锁,但是插入操作回滚时删除主键时调用函数row_undo_ins_remove_clust_rec
,因此新插入行上也没有隐式锁; -
row_undo_ins
函数实现 insert 语句的回滚操作,如锁等待超时、唯一键冲突,其中不会调用标记删除函数,row_upd
函数实现 update/delete 语句的更新操作,其中可能调用标记删除函数。从row_undo_ins
函数的注释中也可以看到对于新插入的行,由于还没有行记录,因此不需要进行标记删除。
/***********************************************************//**
Undoes a fresh insert of a row to a table. A fresh insert means that
the same clustered index unique key did not have any record, even delete
marked, at the time of the insert. InnoDB is eager in a rollback:
if it figures out that an index record will be removed in the purge
anyway, it will remove it in the rollback.
@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
锁等待超时查看事务信息。
---TRANSACTION 137053, ACTIVE 157 sec
1 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 6, OS thread handle 139669941503744, query id 437 127.0.0.1 admin
TABLE LOCK table `test_zk`.`t_lock` trx id 137053 lock mode IX
其中:
-
显示锁信息为空,甚至插入意向锁等待也消失了,表明持锁与等锁信息被清空。
场景 2
测试
在没有数据冲突的前提下,insert 语句除了需要持有插入意向锁,还需要持有其他锁吗?
操作流程
time | session 1 | session 2 |
---|---|---|
1 | begin; update t_lock set c=3 where b=3; |
|
2 | begin; update t_lock set c=4 where b=4; |
|
3 | insert into t_lock(id,a,b,c) values(3,3,3,3); blocking |
|
4 | rollback; |
其中:
-
事务 2 在插入之前同样更新不存在的记录,因此持有间隙锁; -
其他操作保持不变,对比时刻 4 的加锁规则。
时刻 2 查看事务信息
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136385 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
时刻 4 查看事务信息
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136385 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 80000005; asc ;;
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 4; hex 80000003; asc ;;
其中:
-
insert 语句获取到插入意向锁之前持有 gap lock b=5; -
insert 语句获取到插入意向锁之后持有 gap lock b=3、5,显示增加一个间隙锁,表明发生锁分裂; -
原因是同一个事务持有 gap lock 的前提下插入数据,会发生锁分裂,如果是其他事务持有 gap lock,会发生插入意向锁等待。
为获取堆栈信息,新增以下两个断点:
-
lock_rec_insert_check_and_lock
函数,用于插入操作中检查是否发生锁分裂; -
lock_rec_inherit_to_gap_if_gap_lock
函数,用于实现锁分裂。
重新执行插入操作时触发断点,堆栈信息如下所示。
事务 1 回滚后事务 2 继续执行,具体是插入二级非唯一索引。

然后发生锁分裂,将事务 2 已持有的间隙锁分裂成两个间隙锁,即 (1, 5) 变成 (1, 3) 与 (3, 5)。

下面分析原因。
分析
lock_rec_insert_check_and_lock
函数中判断是否发生锁分裂,并将结果保存在inherit
变量中。
ibool inherit_in = *inherit;
trx_t* trx = thr_get_trx(thr);
// 获取下一条记录
const rec_t* next_rec = page_rec_get_next_const(rec);
ulint heap_no = page_rec_get_heap_no(next_rec);
// 判断下一条记录上是否有锁
lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no);
// lock_rec_get_first返回 NULL 表示下一个记录上没有锁,因此使用隐式锁
if (lock == NULL) {
if (inherit_in && !dict_index_is_clust(index)) {
/* Update the page max trx id field */
// 直接更新二级索引trx id
// 更新页的最大事务ID
page_update_max_trx_id(block,
buf_block_get_page_zip(block),
trx->id, mtr);
}
// 不需要锁分裂
*inherit = FALSE;
return(DB_SUCCESS);
}
// 可能需要锁分裂
*inherit = TRUE;
其中:
-
如果插入位置的下一行记录上没有锁,不需要发生锁分裂,否则可能需要发生锁分裂; -
因此同一个事务持有 gap lock 的前提下插入数据,会发生锁分裂,如果是其他事务持有 gap lock,会发生插入意向锁等待。
lock_rec_inherit_to_gap_if_gap_lock
函数用于实现锁分裂。
// 遍历记录上的所有锁
for (lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no);
lock != NULL;
lock = lock_rec_get_next(heap_no, lock)) {
// 如果不是插入意向锁
if (!lock_rec_get_insert_intention(lock)
&& (heap_no == PAGE_HEAP_NO_SUPREMUM
// 是LOCK_GAP或者NEXT-KEY LOCK(没有设置LOCK_REC_NOT_GAP标记)
|| !lock_rec_get_rec_not_gap(lock))) {
// 给事务增加一个新的锁对象,锁的类型为LOCK_REC | LOCK_GAP
// 所有符合条件的会话都继承了这个新的GAP,避免之前的GAP锁失效
lock_rec_add_to_queue(
LOCK_REC | LOCK_GAP | lock_get_mode(lock),
block, heir_heap_no, lock->index,
lock->trx, FALSE);
}
}
其中:
-
如果下一行记录上有锁,且锁的类型不是插入意向锁,且设置 LOCK_REC_NOT_GAP
标记,发生锁分裂; -
锁分裂的具体实现是新增一个间隙锁。
场景 3
测试
实际上,insert 语句在没有数据冲突的场景下,除了需要持有插入意向锁,可能持有间隙锁,还需要持有记录锁。
insert 语句在没有锁冲突的前提下最后一步加记录锁时使用隐式锁,否则发生锁等待,因此正常情况下无法查到隐式锁,但是可以模拟锁冲突,在 insert 语句执行完成后由其他语句将其转换成显式锁。
操作流程
time | session 1 | session 2 |
---|---|---|
1 | begin; update t_lock set c=3 where b=3; |
|
2 | begin; insert into t_lock(id,a,b,c) values(3,3,3,3); blocking |
|
3 | rollback; | |
4 | Query OK, 1 row affected | |
5 | update t_lock set c=3 where b=3; blocking |
其中:
-
事务 1 在 rollback 后再次执行 update 语句,由于数据已存在,且索引类型是二级非唯一键,因此需要申请 b=3 next-key lock; -
由于多个间隙锁之间兼容,因此即使事务 2 insert 发生锁分裂后产生间隙锁,理论上也没有锁冲突,但是测试显示发生锁冲突。
时刻 5 事务信息
---TRANSACTION 136390, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 86, OS thread handle 139726189901568, query id 18261 127.0.0.1 admin updating
update t_lock set c=3 where b=3
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136390 lock_mode X waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 4; hex 80000003; asc ;;
------------------
TABLE LOCK table `test_zk`.`t_lock` trx id 136390 lock mode IX
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136390 lock_mode X waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 4; hex 80000003; asc ;;
---TRANSACTION 136385, ACTIVE 409 sec
...
RECORD LOCKS space id 504 page no 6 n bits 80 index idx_b of table `test_zk`.`t_lock` trx id 136385 lock_mode X locks rec but not gap
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 4; hex 80000003; asc ;;
其中:
-
update where b = 3 时事务 2 insert 新增 record lock b = 3,因此导致事务 1 锁等待; -
测试显示 update where a = 3 时事务 2 insert 新增 record lock a = 3,表明隐式锁转换成显式锁时的加锁索引与新加的锁有关。
为获取堆栈信息,新增以下两个断点:
-
lock_rec_lock
函数,用于加锁,包括加锁成功与加锁失败; -
lock_rec_convert_impl_to_expl
函数,用于将隐式锁转换成显式锁。
事务 1 回滚后事务 2 继续执行时首先判断是否发生锁分裂。

其中:
-
前一节中提到,如果第二行记录 heap_no = 3 上没有锁,将认为不会发生锁分裂并使用隐式锁; -
但是这里发现第二行记录上有锁, type_mode = 2595 = 0b00000000000000000000101000100011
,表明是插入意向锁,因此同样认为可能发生锁分裂。
然后调用锁分裂函数。

其中:
-
由于是插入意向锁,而插入意向锁是特殊的间隙锁,并不是真正的间隙锁,因此判断不需要发生锁分裂; -
由于 type_mode
中第 12 个比特位表示插入意向锁,lock_rec_get_insert_intention
函数用于获取该比特位的值,因此返回 0 表示不是插入意向锁,否则返回 2048。
/*********************************************************************//**
Gets the waiting insert flag of a record lock.
@return LOCK_INSERT_INTENTION or 0 */
UNIV_INLINE
ulint
lock_rec_get_insert_intention(
/*==========================*/
const lock_t* lock) /*!< in: record lock */
{
return(lock->type_mode & LOCK_INSERT_INTENTION);
}
#define LOCK_INSERT_INTENTION 2048
事务 1 重新执行 update 时首先将隐式锁转换成显式锁。

其中:
-
看到了熟悉的函数 row_search_mvcc
,表明对应 update 语句中的查询操作; -
然后调用 lock_rec_convert_impl_to_expl
函数将 update 使用的二级索引上的隐式锁转换成显式锁。
然后 update 语句申请 next-key lock 时发生锁等待。

分析
lock_sec_rec_read_check_and_lock
函数主体代码如下所示。
/* Some transaction may have an implicit x-lock on the record only
if the max trx id for the page >= min trx id for the trx list or a
database recovery is running. */
// 用page的max trx id和当前活跃的最小读写事务进行比对判断,如果大于等于则可能存在隐式锁,然后需要回表通过主键进行精细化判断
// 如果有隐式锁,需要将隐式锁转换成显式锁
if ((page_get_max_trx_id(block->frame) >= trx_rw_min_trx_id()
|| recv_recovery_is_on()) // 崩溃恢复时的事务id可能不是正确的
&& !page_rec_is_supremum(rec)) {
// 检查隐式锁,如果有隐式锁,将其转换成显式锁
lock_rec_convert_impl_to_expl(block, rec, index, offsets);
}
// 对于查询语句,mode | gap_mode 合并生成 mode
// lock_sec_rec_read_check_and_lock,加锁类型由外部传入,不固定
// lock_sec_rec_modify_check_and_lock 加锁类型固定
err = lock_rec_lock(FALSE, mode | gap_mode,
block, heap_no, index, thr);
其中:
-
如果 page 的 max trx id 大于当前活跃的最小读写事务,表明可能存在隐式锁,然后调用 lock_rec_convert_impl_to_expl
函数; -
在处理完隐式锁以后,给查询语句加锁,如果锁冲突,将发生锁等待。
lock_rec_convert_impl_to_expl
函数主体代码如下所示。
// 对于二级索引,通过Page的MAX_TRX_ID判断事务是否活跃
trx = lock_sec_rec_some_has_impl(rec, index, offsets);
// 如果事务处于活跃状态
if (trx != 0) {
/* If the transaction is still active and has no
explicit x-lock set on the record, set one for it.
trx cannot be committed until the ref count is zero. */
// 把 rec 记录上的隐式锁转换为显式锁
// 如果是活跃事务,则将隐式锁转换为显示锁
lock_rec_convert_impl_to_expl_for_trx(
block, rec, index, offsets, trx, heap_no);
}
其中:
-
如果存在活跃事务,将隐式锁转换为显示锁。
总结
到这里,在不考虑唯一索引的前提下,二级非唯一索引插入操作中的加锁规则基本上就介绍完了,流程图如下所示。

其中:
-
首先判断是否发生插入意向锁等待,如果其他事务持有间隙锁,将导致插入意向锁等待; -
插入失败有一种场景,锁等待超时,因此需要将已经插入的主键索引删除,期间不发生锁继承; -
锁等待没有超时时可能发生锁分裂,前提是同一个事务持有间隙锁; -
插入成功后还有可能继续加锁,原因是后续的其他事务可能会将插入操作中的隐式锁转换成显式锁。
结论
insert 语句正常情况下使用隐式锁,在不考虑唯一索引的前提下,二级非唯一索引插入操作中可能持有的锁包括:
-
插入意向锁,可能发生锁等待; -
间隙锁,可能发生锁分裂,注意回滚操作中不会发生锁继承; -
记录锁,可能将隐式锁转换成显式锁,如果在插入成功以后有其他事务可能存在锁冲突。
参考教程
原文始发于微信公众号(丹柿小院):MySQL insert 语句加锁分析(上)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/254023.html