当前位置: 首页 > 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.xdnf.cn/news/1424503.html

相关文章:

  • Linux 进程间通信(IPC)
  • 大型语言模型微调 内容预告(69)
  • 【Docker】2025版Ubuntu 22.04 安装 Docker Docker Compose 指南
  • 电力工程师的AI时代已来,这6大功能彻底颠覆传统工作模式
  • 系统性学习数据结构-第二讲-顺序表与链表
  • 金融数据安全
  • 基于单片机汽车防盗系统/汽车安全防丢系统
  • 动态代理设计模式
  • 多模态大语言模型部署
  • Java泛型通配符详解:搞懂?/extends/super用法,避开集合操作踩坑点
  • 二、感知机
  • 高防IP防护效果评估全攻略:从指标解读到实战测试
  • langgraph / openmanus / suna 对比
  • 数据安全不用愁,群晖NAS让你存得放心、用得安心
  • 深度学习环境搭建运行(二) Ubuntu22.04安装基于CUDA11.8的ONNXRuntime-gpu1.18.1详细步骤(新手入门)
  • 联邦学习的文献复现与创新思路指导
  • Qt 项目文件(.pro)中添加 UI 文件相关命令
  • 深度学习】--卷积神经网络
  • k8s--etcd
  • h5实现内嵌微信小程序支付宝 --截图保存海报分享功能
  • authentication port-control auto 和 dot1x port-control auto
  • Linux ARP老化机制/探测机制/ip neigh使用
  • Paimon MergeTreeWrite、Compaction 和 快照构建
  • 甲烷浓度时空演变趋势分析与异常值计算(附下载脚本)
  • 基于docker-compose搭建EFK(Elasticsearch+fluentd+kibana)的日志平台
  • 2025年工作后值得考的财会行业证书推荐,尤其是第二个!
  • 从网络层接入控制过渡到应用层身份认证的过程
  • 如何在SptingBoot项目中引入swagger生成API文档
  • HarvardX TinyML小笔记2(番外3:数据工程)
  • 技术速递|构建你的第一个 MCP 服务器:如何使用自定义功能扩展 AI 工具