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

MySQL 中 InnoDB 引擎的事务隔离级别与“可重复读”隔离级别下的 SQL 编写规范

文章目录

      • 一、核心概念解析与分类
      • 二、事务隔离级别:问题与解决方案
      • 三、MVCC 与 Undo Log 原理
        • 1. MVCC (多版本并发控制)
        • 2. Undo Log (回滚日志)
      • 四、可重复读 (RR) 隔离级别下的SQL规范


一、核心概念解析与分类

这些概念可以分为三类:读现象锁类型读操作类型

类别概念解释
读现象脏读 (Dirty Read)事务A读取了事务B尚未提交的修改数据。如果事务B回滚,事务A读到的就是无效的“脏”数据。
不可重复读 (Non-Repeatable Read)在事务A内,两次读取同一行数据,得到了不同的结果。这是因为在两次读取之间,这行数据被事务B修改并提交了。
幻读 (Phantom Read)在事务A内,两次执行相同的范围查询,返回的结果集数量不同(出现了新的“幽灵”行或行消失了)。这是因为在两次查询之间,有事务B插入或删除了符合该查询条件的行并提交了。
锁机制行锁 (Row Lock)锁定表中的某一条具体记录。InnoDB的行锁是加在索引项上的。如果WHERE条件无法命中索引,会导致锁表,严重降低并发性能。
间隙锁 (Gap Lock)锁定一个索引值的范围区间(开区间),但不包括记录本身。例如,锁定 (10, 20),是为了防止其他事务在这个区间内插入任何新记录。它是RR级别防幻读的关键。
Next-Key Lock行锁 + 间隙锁 的组合。它会锁定一个范围并且锁定记录本身,形成左开右闭的区间,例如 (10, 20]。它是InnoDB默认的行锁算法。
意向锁 (Intention Lock)一种表级锁。它表示事务“有意向”对表中的某些行加锁。分为:
1. 意向共享锁 (IS):事务打算给某些行加S锁
2. 意向排他锁 (IX):事务打算给某些行加X锁
作用:高效协调行锁与表锁的冲突。例如,事务想加表锁时,只需检查表上是否有意向锁,而无需遍历每一行。
读操作类型快照读 (Snapshot Read)普通的 SELECT 语句(不加 FOR UPDATE 等)。它基于 MVCC 机制,读取数据在某个时间点的一致性快照(历史版本),不加锁,读写操作不互相阻塞。
当前读 (Current Read)读取数据的最新已提交版本,并会对其加锁。包括:
1. SELECT ... FOR UPDATE (加X锁)
2. SELECT ... LOCK IN SHARE MODE (加S锁)
3. INSERT, UPDATE, DELETE (加X锁)
用于保证数据在逻辑上的最新性和一致性。

二、事务隔离级别:问题与解决方案

SQL标准定义了4个隔离级别,InnoDB通过 MVCC(读操作)锁机制(写操作/当前读) 来实现它们。

隔离级别解决问题InnoDB 如何解决?遗留问题
读未提交 (RU)几乎不解决。直接读取数据页的最新版本,无论其他事务是否提交。所有并发问题都可能发生(脏读、不可重复读、幻读)。
读已提交 (RC)脏读MVCC:每次快照读都会生成一个独立的、最新的 ReadView。因此,每次读到的都是最新已提交的数据,避免了脏读。不可重复读幻读可能发生。因为每次读都会生成新的ReadView,其他事务的提交会立刻被看到。
可重复读 (RR)脏读不可重复读1. MVCC:在第一次快照读时生成一个 ReadView,整个事务期间都复用这个快照,实现可重复读。
2. 锁机制:对当前读操作使用 Next-Key Lock,锁住记录及其周围的间隙,防止其他事务插入新行,从而在绝大多数情况下避免了幻读
理论上幻读可能发生,但InnoDB通过Next-Key Lock机制在实际应用中几乎完全解决了幻读。这是InnoDB对标准隔离级别的增强。
串行化 (S)所有问题强制加锁:将所有普通的快照读 (SELECT) 都转换为当前读 (SELECT ... LOCK IN SHARE MODE),读写严重冲突,事务串行执行。性能极低,因为大量的锁竞争和超时。

三、MVCC 与 Undo Log 原理

1. MVCC (多版本并发控制)

目的:实现非阻塞的快照读,提高数据库的并发性能。
核心思想:为每一行数据维护多个历史版本,使读操作和写操作可以互不干扰。

关键实现要素

  • 隐藏字段:InnoDB每行记录都有两个隐藏字段。
    • DB_TRX_ID (6字节):记录最后修改此行数据的事务ID。
    • DB_ROLL_PTR (7字节):回滚指针,指向该行数据在 Undo Log 中的上一个历史版本。
  • ReadView (读视图):事务进行快照读时产生的数据结构,决定了当前事务能看到哪个版本的数据。它包含:
    • m_ids:生成ReadView时,系统中所有活跃(未提交)事务ID的列表。
    • min_trx_idm_ids中的最小值。
    • max_trx_id:生成ReadView时,系统下一个将要分配的事务ID。
    • creator_trx_id:创建该ReadView的事务ID。

可见性算法
当读取一行记录时,InnoDB会遍历其Undo Log版本链,并检查链中每个版本的 DB_TRX_ID

  1. 如果 DB_TRX_ID < min_trx_id,说明该版本在ReadView创建前已提交,可见
  2. 如果 DB_TRX_ID >= max_trx_id,说明该版本在ReadView创建后才开启,不可见
  3. 如果 min_trx_id <= DB_TRX_ID < max_trx_id,则检查 DB_TRX_ID 是否在 m_ids 中:
    • 如果在,说明该版本是由当时还未提交的事务修改的,不可见
    • 如果不在,说明该版本当时已提交,可见
  4. 如果当前记录版本不可见,则沿着 DB_ROLL_PTR 指针找到上一个历史版本,重复上述判断,直到找到可见的版本或遍历完链。

不同级别的区别

  • RC每次快照读都生成一个新的ReadView。所以总能读到最新提交的数据。
  • RR:只在第一次快照读时生成一个ReadView,后续都复用。所以整个事务看到的数据是一致的。
2. Undo Log (回滚日志)

作用

  1. 事务回滚:记录数据修改前的值,用于事务失败时恢复原状。
  2. 实现MVCC:存储数据的历史版本。当某个旧版本数据需要被快照读访问时,通过回滚指针 DB_ROLL_PTR 在Undo Log中构建出版本链。

生命周期

  • 当执行 INSERTUPDATEDELETE 时,会将修改前的数据(旧版本)写入Undo Log。
  • 这些Undo Log页面只有在所有依赖于它们的事务都提交后,才可以被 Purge线程 安全地删除。

四、可重复读 (RR) 隔离级别下的SQL规范

在日常工作中,为了正确利用RR级别的特性并避免潜在问题,应遵循以下规范:

  1. 明确区分读操作类型

    • 快照读 (SELECT):用于纯查询、报表生成、数据展示等只读场景。享受无锁并发的高性能。
    • 当前读 (SELECT ... FOR UPDATE):用于“先查后改”的业务场景。例如,检查账户余额后扣款。这能锁定相关行和间隙,防止其他事务修改,确保后续操作的安全性。
  2. 确保SQL语句正确使用索引

    • UPDATEDELETESELECT ... FOR UPDATEWHERE 条件必须命中索引
    • 原因:InnoDB的行锁和间隙锁是加在索引上的。如果没有索引,InnoDB无法精确定位行,会退化为锁住整个表或大量的间隙,导致并发性能急剧下降死锁风险显著增高
  3. 控制事务粒度,尽快提交

    • 避免长事务:长事务会长期持有ReadView,导致大量的Undo Log无法被Purge,占用存储空间。同时,长期持有所也会阻塞其他事务,成为性能瓶颈。
    • 业务逻辑应尽快提交:完成操作后立即提交事务,释放锁资源。
  4. 显式处理“丢失更新”

    • RR级别不能天然解决“丢失更新”(两个事务读取同一值,分别计算后更新,后提交的覆盖了先提交的)。
    • 解决方案:在查询时直接使用 SELECT ... FOR UPDATE 对目标数据加排他锁,阻止其他事务同时修改。

示例:安全的余额扣减操作

START TRANSACTION;-- 1. 使用当前读锁定目标行(假设id是主键索引)
SELECT balance FROM accounts WHERE id = 123 FOR UPDATE;-- 2. 在应用层判断余额是否足够,并进行计算-- 3. 执行更新(由于行已被锁定,其他事务无法修改,保证安全)
UPDATE accounts SET balance = balance - 100 WHERE id = 123;COMMIT; -- 提交后释放锁

通过遵循这些规范,您可以在RR隔离级别下构建出既高效又安全的数据访问逻辑。

http://www.dtcms.com/a/361843.html

相关文章:

  • 2025 中国算力大会精彩回顾:算力驱动未来,液冷引领革新
  • 从 Oracle 到 TiDB,通过ETL工具,高效实现数据拉通
  • HarvardX TinyML小笔记2(番外3:数据工程)(TODO)
  • 【C++游记】AVL树
  • 通过 select into outfile / load data infile 进行数据导入导出学习笔记
  • 【网络安全入门基础教程】网络安全就业方向(非常详细)零基础入门到精通,收藏这篇就够了
  • three.js+WebGL踩坑经验合集(10.2):镜像问题又一坑——THREE.InstancedMesh的正反面向光问题
  • 亥姆霍兹线圈和放载流线圈
  • 【SpreadJS V18.2 新特性】Table 与 DataTable 双向转换功能详解
  • SD卡自动检测与挂载脚本
  • React 第七十一节 Router中generatePath的使用详解及注意事项
  • table表格字段明细展示
  • 【前端教程】ES6 Promise 实战教程:从基础到游戏案例
  • django的URL路由配置常用方式
  • C# Task 入门:让你的程序告别卡顿
  • 基于STM32单片机的无线鼠标设计
  • 【ComfyUI】图像反推描述词总结
  • 杰理ac791无法控制io脚原因
  • 【算法】算法题核心类别与通用解题思路
  • 时序数据库IoTDB:为何成为工业数据管理新宠?
  • 【frontend】w3c的发展历史ToDo
  • accelerate、trainer、lightning还是pytorch?
  • SpringBoot 分库分表 - 实现、配置与优化
  • 雅思听力第四课:配对题核心技巧与词汇深化
  • CLion编译基于WSL平台Ubuntu系统的ros项目
  • 1.人工智能——概述
  • 测试开发的角色
  • 动态规划:硬币兑换II
  • 异常类分析
  • HTML应用指南:利用GET请求获取全国招商银行网点位置信息