MySQL insert 语句加锁分析(上)

引言

在分析了 update 与 delete 语句的加锁流程后,本文分析 insert 语句的加锁流程,主要分为以下两种场景:

  • 待插入记录的下一条记录上已经被其他事务加了间隙锁时
  • 遇到冲突键时

每种场景又可以细分为三种场景,由于篇幅原因,全文拆分为两篇文章,本文是第一篇文章,分析第一种场景,也就是不考虑唯一索引的场景。

由于个人能力有限,以下内容可能有误,欢迎批评指正。

在此之前建议阅读之前的两篇文章:

准备工作

测试数据

数据库版本:5.7.24

事务隔离级别:RR

测试数据

mysql> show create table t_lock G
*************************** 1. row ***************************
       Table: tt
Create TableCREATE TABLE `t_lock` (
  `id` int(11NOT NULL AUTO_INCREMENT,
  `a` int(11DEFAULT '0',
  `b` int(11DEFAULT '0',
  `c` int(11DEFAULT '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(1111),(5555),(9999);
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
lock struct(s), heap size 11360 row lock(s), undo log entries 1
MySQL thread id 81, OS thread handle 139726190700288query 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
lock struct(s), heap size 11361 row lock(s)
MySQL thread id 86, OS thread handle 139726189901568query 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 lockheap 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 1locked 1
LOCK WAIT 2 lock struct(s), heap size 11361 row lock(s), undo log entries 1
MySQL thread id 87, OS thread handle 139726190434048query 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 lockheap 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 lockheap 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
lock struct(s), heap size 11361 row lock(s), undo log entries 1
MySQL thread id 87, OS thread handle 139726190434048query 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 lockheap 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函数,用于在发生锁等待时进行加锁,其中进行死锁检测。

重新执行插入操作时触发断点,堆栈信息如下所示。

首先插入主键前检查是否发生插入意向锁等待,判断不需要等待,因此主键索引插入成功。

MySQL insert 语句加锁分析(上)

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

MySQL insert 语句加锁分析(上)

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

MySQL insert 语句加锁分析(上)

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

MySQL insert 语句加锁分析(上)

分析

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函数设置断点,该函数用于删除操作过程中的锁继承。

在锁等待超时后,堆栈信息如下所示。

MySQL insert 语句加锁分析(上)

其中:

  • 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
lock struct(s), heap size 11361 row lock(s)
MySQL thread id 6, OS thread handle 139669941503744query 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 lockheap 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 lockheap 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 lockheap 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 继续执行,具体是插入二级非唯一索引。

MySQL insert 语句加锁分析(上)

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

MySQL insert 语句加锁分析(上)

下面分析原因。

分析

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 1locked 1
LOCK WAIT 2 lock struct(s), heap size 11361 row lock(s)
MySQL thread id 86, OS thread handle 139726189901568query 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 lockheap 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 lockheap 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 lockheap 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 继续执行时首先判断是否发生锁分裂。

MySQL insert 语句加锁分析(上)

其中:

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

然后调用锁分裂函数。

MySQL insert 语句加锁分析(上)

其中:

  • 由于是插入意向锁,而插入意向锁是特殊的间隙锁,并不是真正的间隙锁,因此判断不需要发生锁分裂;
  • 由于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 时首先将隐式锁转换成显式锁。

MySQL insert 语句加锁分析(上)

其中:

  • 看到了熟悉的函数row_search_mvcc,表明对应 update 语句中的查询操作;
  • 然后调用lock_rec_convert_impl_to_expl函数将 update 使用的二级索引上的隐式锁转换成显式锁。

然后 update 语句申请 next-key lock 时发生锁等待。

MySQL insert 语句加锁分析(上)

分析

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);
 }

其中:

  • 如果存在活跃事务,将隐式锁转换为显示锁。

总结

到这里,在不考虑唯一索引的前提下,二级非唯一索引插入操作中的加锁规则基本上就介绍完了,流程图如下所示。

MySQL insert 语句加锁分析(上)

其中:

  • 首先判断是否发生插入意向锁等待,如果其他事务持有间隙锁,将导致插入意向锁等待;
  • 插入失败有一种场景,锁等待超时,因此需要将已经插入的主键索引删除,期间不发生锁继承;
  • 锁等待没有超时时可能发生锁分裂,前提是同一个事务持有间隙锁;
  • 插入成功后还有可能继续加锁,原因是后续的其他事务可能会将插入操作中的隐式锁转换成显式锁。

结论

insert 语句正常情况下使用隐式锁,在不考虑唯一索引的前提下,二级非唯一索引插入操作中可能持有的锁包括:

  • 插入意向锁,可能发生锁等待;
  • 间隙锁,可能发生锁分裂,注意回滚操作中不会发生锁继承;
  • 记录锁,可能将隐式锁转换成显式锁,如果在插入成功以后有其他事务可能存在锁冲突。

参考教程

原文始发于微信公众号(丹柿小院):MySQL insert 语句加锁分析(上)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/254023.html

(0)
Java朝阳的头像Java朝阳

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!