当前位置: 首页 > news >正文

MySQL 唯一索引下先事务A插入再事务B当前读是否阻塞问题

文章目录

  • 唯一索引下先事务A插入再事务B当前读是否阻塞问题
    • 场景模拟
    • 扩展场景 1:事务 B 当前读 where col > 6
    • 扩展场景 2:事务 B 当前读 where col >= 6
    • 扩展场景 3:事务 B 当前读 where col >= 5

唯一索引下先事务A插入再事务B当前读是否阻塞问题

场景模拟

场景描述:可重复读隔离级别下,一个唯一索引,有数据 5、9,先事务 A 插入数据 6,然后事务 B 当前读 where col > 7,问事务 B 是否阻塞?

假设有一张 student 表,对其中字段 age 建立唯一索引,并已存在两条 age 分别为 5、9 的记录。

DROP TABLE IF EXISTS student;
CREATE TABLE student(
	id INT UNSIGNED AUTO_INCREMENT COMMENT '主键',
	age TINYINT UNSIGNED NOT NULL UNIQUE COMMENT '年龄',
	PRIMARY KEY(id)
);

INSERT INTO student(age) VALUES
(5),
(9);

之后,有如下流程:

  1. 先事务 A 插入一条 age = 6 的记录。
  2. 然后事务 B 当前读 where age > 7。

先事务 A 插入一条 age = 6 的记录。

image-20250304000033141

insert 语句在正常执行时是不会生成锁结构的,它是靠聚簇索引记录自带的 trx_id 隐藏列来作为隐式锁来保护记录的。

当事务需要加锁时,如果这个锁不可能发生冲突,InnoDB 会跳过加锁环节,这种机制称为隐式锁。隐式锁是 InnoDB 实现的一种延迟加锁机制,其特点是只有在可能发生冲突时才加锁,从而减少了锁的数量,提高了系统整体性能。

每 insert 插入一条新记录,RR 隔离级别下为了防止幻读,都需要看一下待插入记录的下一条记录上是否已经被加了间隙锁。

  • 如果已加间隙锁,此时会生成一个插入意向锁,然后锁的状态设置为等待状态。
  • 如果没有间隙锁,则无需生成插入意向锁,InnoDB 会跳过加锁环节,也就是隐式锁机制。只有在插入记录之后,特殊情况下,才会将隐式锁转换为显式锁(X 型的记录锁)。

我们来观察一下事务 A 插入一条 age = 6 的记录后的锁状态,执行 select * from performance_schema.data_locks\G; 语句。

image-20250304001001336

可以看到,并不存在行级锁,只有一个表级别的意向独占锁被事务 A 持有,这表明在事务 A 的后续操作中可能会加行级别的独占锁。这其实就是在之前提到的「只有在插入记录之后,特殊情况下,才会将隐式锁转换为显式锁(X 型的记录锁)」。

然后事务 B 当前读 where age > 7。

image-20250304001258751

发现事务 B 并没有阻塞

再来看一下当前的锁状态,执行 select * from performance_schema.data_locks\G; 语句。略过表级锁和主键加锁情况,这里只看值得关注的。

image-20250304001652662

可以发现事务 A 并没有加行级锁,也就是还是隐式锁状态。而事务 B 加了两个行级锁:

  1. 在最大虚拟记录 supremum 上,加了 X 型的临键锁,锁住范围 (9, +∞)
  2. 在索引记录 age = 9 上,加了 X 型的临键锁,锁住范围 (6, 9]

原因分析:

  • 当前读加间隙锁的目的是避免幻读,也就是说事务 B 只是希望在范围 (7, +∞) 内没有新增记录,并不需要获取不在自己当前读区间 age=6 的独占锁
  • 无论有没有其他事务先新增、修改、删除、当前读了 age=6 这条记录,都不影响事务 B 对范围 (7, +∞) 的独占统治。
  • 事务 B 在加锁时会找到比 7 大的下一条记录,也就是 age=9,给它加 X 型临键锁。

扩展场景 1:事务 B 当前读 where col > 6

场景描述:可重复读隔离级别下,一个唯一索引,有数据 5、9,先事务 A 插入数据 6,然后事务 B 当前读 where col > 6,问事务 B 是否阻塞?

假设有一张 student 表,对其中字段 age 建立唯一索引,并已存在两条 age 分别为 5、9 的记录。

DROP TABLE IF EXISTS student;
CREATE TABLE student(
	id INT UNSIGNED AUTO_INCREMENT COMMENT '主键',
	age TINYINT UNSIGNED NOT NULL UNIQUE COMMENT '年龄',
	PRIMARY KEY(id)
);

INSERT INTO student(age) VALUES
(5),
(9);

之后,有如下流程:

  1. 先事务 A 插入一条 age = 6 的记录。
  2. 然后事务 B 当前读 where age > 6。

先事务 A 插入一条 age = 6 的记录。

image-20250304003658288

然后事务 B 当前读 where age > 6。

image-20250304003814637

发现事务 B 并没有阻塞

再来看一下当前的锁状态,执行 select * from performance_schema.data_locks\G; 语句。略过表级锁和主键加锁情况,这里只看值得关注的。

image-20250304003906413

可以发现事务 A 并没有加行级锁,也就是还是隐式锁状态。而事务 B 加了两个行级锁:

  1. 在最大虚拟记录 supremum 上,加了 X 型的临键锁,锁住范围 (9, +∞)
  2. 在索引记录 age = 9 上,加了 X 型的临键锁,锁住范围 (6, 9]

原因分析:

  • 当前读加间隙锁的目的是避免幻读,也就是说事务 B 只是希望在范围 (6, +∞) 内没有新增记录,并不需要获取不在自己当前读区间 age=6 的独占锁
  • 无论有没有其他事务先新增、修改、删除、当前读了 age=6 这条记录,都不影响事务 B 对范围 (6, +∞) 的独占统治。
  • 事务 B 在加锁时会找到比 6 大的下一条记录,也就是 age=9,给它加 X 型临键锁。

扩展场景 2:事务 B 当前读 where col >= 6

场景描述:可重复读隔离级别下,一个唯一索引,有数据 5、9,先事务 A 插入数据 6,然后事务 B 当前读 where col >= 6,问事务 B 是否阻塞?

假设有一张 student 表,对其中字段 age 建立唯一索引,并已存在两条 age 分别为 5、9 的记录。

DROP TABLE IF EXISTS student;
CREATE TABLE student(
	id INT UNSIGNED AUTO_INCREMENT COMMENT '主键',
	age TINYINT UNSIGNED NOT NULL UNIQUE DEFAULT 0 COMMENT '年龄',
	PRIMARY KEY(id)
);

INSERT INTO student(age) VALUES
(5),
(9);

之后,有如下流程:

  1. 先事务 A 插入一条 age = 6 的记录。
  2. 然后事务 B 当前读 where age >= 6。

先事务 A 插入一条 age = 6 的记录。

image-20250304003658288

然后事务 B 当前读 where age >= 6。

image-20250304004238532

发现事务 B 阻塞了

再来看一下当前的锁状态,执行 select * from performance_schema.data_locks\G; 语句。略过表级锁和主键加锁情况,这里只看值得关注的。

image-20250304010736371

image-20250304010929681

可以发现,事务 A 从隐式锁转为了显示锁,即持有 age=6 的 X 型记录锁状态。而事务 B 需要等待获取 age=6 的 X 型临键锁。

原因分析:

  • 当前读加间隙锁的目的是避免幻读,也就是说事务 B 只是希望在范围 [6, +∞) 内没有新增记录,就必须获取在自己当前读区间 age=6 的独占锁
  • 事务 B 在加锁时发现 age=6 这条记录存在,就需要给这条记录加 X 型临键锁。
  • 而事务 A 从隐式锁转为了显示锁,持有 age=6 的 X 型记录锁,事务 B 就需要等待事务 A 释放锁,才能给这条记录加 X 型临键锁。
  • 事务 B 在当前读下的锁等待与获取保证了不会出现脏读、不可重复读和幻读问题。

扩展场景 3:事务 B 当前读 where col >= 5

场景描述:可重复读隔离级别下,一个唯一索引,有数据 5、9,先事务 A 插入数据 6,然后事务 B 当前读 where col >= 5,问事务 B 是否阻塞?

假设有一张 student 表,对其中字段 age 建立唯一索引,并已存在两条 age 分别为 5、9 的记录。

DROP TABLE IF EXISTS student;
CREATE TABLE student(
	id INT UNSIGNED AUTO_INCREMENT COMMENT '主键',
	age TINYINT UNSIGNED NOT NULL UNIQUE DEFAULT 0 COMMENT '年龄',
	PRIMARY KEY(id)
);

INSERT INTO student(age) VALUES
(5),
(9);

之后,有如下流程:

  1. 先事务 A 插入一条 age = 6 的记录。
  2. 然后事务 B 当前读 where age >= 5。

先事务 A 插入一条 age = 6 的记录。

image-20250304003658288

然后事务 B 当前读 where age >= 5。

image-20250304011925741

发现事务 B 阻塞了

再来看一下当前的锁状态,执行 select * from performance_schema.data_locks\G; 语句。略过表级锁和主键加锁情况,这里只看值得关注的。

image-20250304012124189

image-20250304012532715

image-20250304012349957

可以发现,事务 A 从隐式锁转为了显示锁,即持有 age=6 的 X 型记录锁状态。而事务 B 需要等待获取 age=6 的 X 型临键锁。

原因分析:

  • 当前读加间隙锁的目的是避免幻读,也就是说事务 B 只是希望在范围 [5, +∞) 内没有新增记录,就必须获取在自己当前读区间 age=6 的独占锁
  • 事务 B 在加锁时发现 age=6 这条记录存在,就需要给这条记录加 X 型临键锁。
  • 而事务 A 从隐式锁转为了显示锁,持有 age=6 的 X 型记录锁,事务 B 就需要等待事务 A 释放锁,才能给这条记录加 X 型临键锁。
  • 事务 B 在当前读下的锁等待与获取保证了不会出现脏读、不可重复读和幻读问题。

相关文章:

  • vue3中 组合式~测试深入组件:事件 与 $emit()—setup() 上下文对象ctx.emit
  • 前端文件加载耗时过长解决方案
  • 网络安全等级保护—定级
  • DeepSeek 3FS:端到端无缓存的存储新范式
  • Docker运行hello-world镜像失败或超时:Unable to find image ‘hello-world:latest‘ locally Trying to pull reposi
  • 安科瑞新能源充电桩解决方案:驱动绿色未来,赋能智慧能源
  • 利用50张可视化动图理解Mamba与状态空间模型
  • slam学习笔记9---ubuntu2004部署interactive_slam踩坑记录
  • 最新版本TOMCAT+IntelliJ IDEA+MAVEN项目创建(JAVAWEB)
  • Android MVI架构模式详解
  • Ubuntu20.04本地配置IsaacLab 4.2.0的G1训练环境(一)
  • 嵌入式硬件设计SPI时需要注意什么?
  • Spring中的事务管理是如何实现的?
  • 信奥赛CSP-J复赛集训(模拟算法专题)(1):P8813 [CSP-J 2022] 乘方
  • 从零开始学机器学习——什么是机器学习
  • 安卓音频框架混音器
  • java泛型(详细)
  • Vue 系列之:组件通讯
  • Docker基础篇——什么是Docker与Docker的仓库、镜像、容器三大概念
  • CS144 Lab Checkpoint 2: the TCP receiver
  • 奇艺广州网站建设熊掌号/网上培训机构
  • 网络设计报告提纲范文/怎样进行seo优化
  • qq客服代码放在网站哪里/抖音推广
  • 适合seo优化的网站制作/百度seo搜索引擎优化方案
  • html5 + css3 网站/杭州网站推广优化
  • 重庆做商城网站建设/网络营销推广的概念