MySQL 记录锁+间隙锁防幻读方案

server/2024/12/15 16:39:14/

1. 什么是幻读?

定义
        幻读是指在事务过程中,一个事务执行了某些查询操作后,另一事务插入、删除或更新了满足查询条件的新数据,当第一个事务再次执行相同的查询操作时,结果集出现了“幻觉”,即读取到(或缺少)先前查询中不存在的数据。

幻读与脏读、不可重复读的区别

  1. 脏读:事务读取了另一个事务未提交的数据,可能会读到错误结果。
  2. 不可重复读:事务在两次读取中,读到的数据被其他事务修改了,数据值不同。
  3. 幻读:事务在两次读取中,结果集的记录条数或范围变化了,比如多了新记录或少了某些记录。

举例说明
假设有一个数据库employees,内容如下:

idnamesalary
1Alice5000
2Bob4000
3Charlie3000

场景
事务A执行如下查询:

SELECT * FROM employees WHERE salary > 3000;

结果为:

idnamesalary
1Alice5000
2Bob4000

这时,事务B插入了一条新记录:

INSERT INTO employees (id, name, salary) VALUES (4, 'David', 4500);

如果事务A再次执行相同的查询操作,结果会变为:

idnamesalary
1Alice5000
2Bob4000
4David4500

事务A“感知”到了新数据的插入,这种现象就是幻读。

2. MySQL 是如何解决幻读的?

MySQL 通过两种方式解决幻读问题:

  1. 针对快照读:使用 MVCC(多版本并发控制)
  2. 针对当前读:通过加锁机制(记录锁和间隙锁)

(1)针对快照读(Snapshot Read)

快照读的定义
        快照读是一种基于 MVCC 的非阻塞读取方式。执行普通 SELECT 语句时,InnoDB 存储引擎会基于当前事务启动时的数据库版本生成一个数据快照,事务读取的内容固定为快照中的数据,忽略其他事务的更新、插入或删除操作。

如何实现快照读

  • MVCC 原理:InnoDB 为每行记录维护两个隐藏的时间戳字段:trx_id(创建该记录的事务ID)和 roll_pointer(指向回滚日志的指针)。
    • trx_id 用于标记这条记录由哪个事务创建。
    • roll_pointer 用于回滚到旧版本数据,从而形成多个版本链。
  • ReadView
    • 每个事务启动时会生成一个 ReadView,其中包含当前正在活跃事务的 ID 列表。
    • 事务读取数据时,会根据 trx_idReadView 判断数据版本是否可见。

快照读的优点

  1. 高性能:读取操作不需要等待锁释放,可以并发执行。
  2. 一致性:避免了其他事务导致的数据不可重复读或幻读现象。

快照读的限制
快照读只能用于非锁定查询,例如:

SELECT * FROM table_name WHERE ...;

(2)针对当前读(Current Read)

当前读的定义
        当前读是一种锁定读取方式,事务在读取数据的同时,会对涉及的数据范围加锁,防止其他事务对数据进行修改或插入。

当前读的典型语句
以下语句属于当前读:

  • SELECT ... FOR UPDATE;
  • SELECT ... LOCK IN SHARE MODE;
  • INSERT, UPDATE, DELETE

加锁机制:记录锁和间隙锁(Next-Key Lock)

为了防止当前读场景下的幻读,MySQL 使用以下两种锁:

  1. 记录锁(Record Lock)

    • 锁定单条记录。
    • 防止其他事务修改或删除这条记录。
  2. 间隙锁(Gap Lock)

    • 锁定一个记录范围之间的“间隙”,即不存在记录的区域。
    • 防止其他事务向这个间隙中插入新记录。
  3. Next-Key Lock(记录锁 + 间隙锁)

    • 锁定一个范围,同时包含范围内的所有记录和间隙。
    • Next-Key Lock 是记录锁和间隙锁的组合,用于防止当前读下的幻读。

3. 通过记录锁和间隙锁防止删除操作导致的幻读的示例

        我们以一个具体场景为例,说明 MySQL 如何通过记录锁和间隙锁防止幻读。

场景描述

假设有一个名为 orders 的表,存储如下数据:

idorder_nameamount
1Order_A100
2Order_B200
4Order_D400

事务过程:

步骤 1:事务A执行当前读
事务A读取表中 amount > 100 的记录并对这些记录加锁:

SELECT * FROM orders WHERE amount > 100 FOR UPDATE;

事务A读取到:

idorder_nameamount
2Order_B200
4Order_D400

加锁分析:

  • 记录锁:锁住符合条件的记录 id=2id=4
  • 间隙锁:锁住 (100, 200)(200, 400) 之间的间隙,以及 (400, +∞) 的间隙。

步骤 2:事务B尝试插入新记录
事务B尝试插入一条新记录:

INSERT INTO orders (id, order_name, amount) VALUES (3, 'Order_C', 300);

事务B会被阻塞,原因如下:

  • 插入的 amount=300 落在 (200, 400) 的间隙范围,而事务A持有该范围的间隙锁,禁止其他事务在此区间插入记录。

步骤 3:事务B尝试删除或修改记录
事务B尝试删除或修改已存在的记录:

DELETE FROM orders WHERE id = 2;

事务B再次被阻塞,原因如下:

  • id=2 的记录已被事务A加了记录锁,其他事务无法删除或修改这条记录。

步骤 4:事务A提交后,事务B执行成功
当事务A完成操作并提交后,锁释放:

  • 事务B的插入操作 id=3, amount=300 可以成功执行。
  • 事务B的删除操作 id=2 也可以正常进行。

总结加锁机制防幻读的过程:

  1. 记录锁:防止其他事务修改或删除当前事务查询到的记录。
  2. 间隙锁:防止其他事务在查询范围内插入新记录。
  3. Next-Key Lock(记录锁 + 间隙锁):通过同时锁定记录和范围,有效防止幻读。

4. 总结

幻读的定义

幻读是指在事务中,某次查询范围内新增了数据,导致后续查询结果不一致。例如,第一次查询到的结果集中没有一条记录,但后续由于其他事务的插入操作,查询结果发生变化。

MySQL 解决幻读的两种方式:

  1. 快照读(Snapshot Read):使用 MVCC 技术

    • 基于多版本并发控制 (MVCC),通过维护数据的多个版本及事务的 ReadView,实现一致性读取。
    • 快照读适用于非阻塞读取操作,如普通的 SELECT 查询。
  2. 当前读(Current Read):使用加锁机制

    • 对读取范围内的记录和间隙加锁,通过记录锁(Record Lock)和间隙锁(Gap Lock)防止插入或修改,避免幻读。
    • 当前读适用于需要锁定数据的操作,例如 SELECT ... FOR UPDATEUPDATEDELETE

加锁机制:记录锁和间隙锁

  • 记录锁:锁定查询范围内的记录,防止其他事务修改或删除这些记录。
  • 间隙锁:锁定查询范围内的“间隙”,防止其他事务向这些间隙插入新记录。
  • Next-Key Lock:记录锁和间隙锁的结合,既锁定记录,又锁定范围,是防止幻读的关键机制。

示例回顾:通过记录锁和间隙锁防止删除操作导致的幻读

在示例中,我们通过以下步骤,详细分析了 MySQL 如何防止幻读:

  1. 事务A对查询范围内的记录和间隙加锁(Next-Key Lock)。
  2. 事务B试图插入新记录或修改已锁定的记录时被阻塞。
  3. 事务A提交后,锁释放,事务B的操作得以继续。

通过 Next-Key Lock 机制,MySQL 确保了事务A的查询范围内的数据不被其他事务破坏,从而避免了幻读现象。

总结价值与启发

  1. 事务隔离级别和加锁机制密切相关:了解幻读的解决方式有助于更好地设计事务逻辑,避免数据一致性问题。
  2. 性能与一致性的权衡:MVCC 提高了读取性能,但需要额外的存储和计算;锁机制确保了当前读的一致性,但可能引发锁争用问题。
  3. 应用场景选择:根据具体需求选择快照读或当前读策略。例如,在频繁更新的场景中,通过加锁防止幻读更为可靠。


http://www.ppmy.cn/server/150391.html

相关文章

docker tdengine windows快速体验

#拉取镜像 docker pull tdengine/tdengine:2.6.0.34#容器运行 docker run -d --name td2.6 --restartalways -p 6030:6030 -p 6041:6041 -p 6043:6043 -p 6044-6049:6044-6049 -p 6044-6045:6044-6045/udp -p 6060:6060 tdengine/tdengine:2.6.0.34#容器数据持久化到本地 #/va…

双重AEB:将基于规则的方法与多模态大型语言模型相结合,以实现有效的紧急制动(202410)

Dual-AEB: Synergizing Rule-Based and Multimodal Large Language Models for Effective Emergency Braking 双重AEB:将基于规则的方法与多模态大型语言模型相结合,以实现有效的紧急制动 Abstract Automatic Emergency Braking (AEB) systems are a c…

前端切换端口,系统前后端交互就报401错误(未授权)的常见原因

当前端切换端口后,前后端交互出现 401 未授权 错误,通常是因为以下原因: 1. 跨域问题 跨域是最常见的原因。前端端口变化会导致浏览器认为请求是跨域的,后端如果未正确配置 CORS,会拒绝请求。 原因: 不同端…

【图像处理】利用numpy、opencv、python实现车牌检测

| 利用opencv实现车牌检测 整体流程涉及5个部分 图像通道转换对比度增强边缘连接二值化边界区域裁剪 图像通道转换 将RGB图像转换为HSV图像,仅保留V通道。V通道表示颜色的明暗,常用于图像对比度拉伸、直方图均衡化等流程。 原图像: V通…

ubuntu 使用 Times New Roman 字体在 Matplotlib 中绘图并调整字体大小

ubuntu 使用 Times New Roman 字体在 Matplotlib 中绘图并调整字体大小 文章目录 ubuntu 使用 Times New Roman 字体在 Matplotlib 中绘图并调整字体大小1. 安装 Times New Roman 字体验证字体是否安装成功 2. 在 Matplotlib 中加载 Times New Roman 字体3. 在 Matplotlib 中使…

微信小程序5-图片实现点击动作和动态加载同类数据

搜索 微信小程序 “动物觅踪” 观看效果 感谢阅读,初学小白,有错指正。 一、功能描述 a. 原本想通过按钮加载背景图片,来实现一个可以点击的搜索button,但是遇到两个难点,一是按钮大小调整不方便(网上搜索…

1.5 多媒体系统简介

目录 多媒体系统声音图形与图像动画和视频 多媒体系统 多媒体可分为感觉媒体、表示媒体、表现媒体、交换媒体。 感觉媒体:直接使人产生感觉的媒体,比如声音、图像、视频。表示媒体:计算机中记录感觉的数据格式。表现媒体:记录感觉…

CTFshow-命令执行(Web41-57)

CTFshow-命令执行(Web41-57) CTFWeb-命令执行漏洞过滤的绕过姿势_绕过空格过滤-CSDN博客 总结rce(远程代码执行各种sao姿势)绕过bypass_远程命令执行绕过-CSDN博客 对比两者的源代码,我们发现,cat指令把flag.php的内容导出后依…