ACID 是什么

  • 原子性:事务中的操作全部成功或全部回滚,主要依赖 Undo Log。
  • 一致性:事务执行前后业务约束保持成立,是前三项共同服务的目标。
  • 隔离性:并发事务互相隔离,通过 MVCC 和锁实现。
  • 持久性:事务提交后的数据可以恢复,主要依赖 Redo Log 和刷盘机制。

四种隔离级别

隔离级别 脏读 不可重复读 幻读
Read Uncommitted 可能 可能 可能
Read Committed 避免 可能 可能
Repeatable Read 避免 避免 需结合具体读方式分析
Serializable 避免 避免 避免

InnoDB 默认通常是 Repeatable Read,但部署环境可以修改,应用不应凭印象假设。

MVCC 如何工作

InnoDB 的聚簇索引记录包含隐藏事务信息。修改记录时,旧版本通过 Undo Log 形成版本链。Read View 保存活跃事务范围,查询根据可见性规则沿版本链找到自己能看到的版本。

在 Read Committed 下,一般每条一致性读语句创建新的 Read View;在 Repeatable Read 下,事务内首次一致性读建立的视图通常会被后续一致性读复用,因此能够重复读取相同版本。

快照读与当前读

普通 SELECT 通常是快照读,读取符合 Read View 的历史版本。SELECT ... FOR UPDATEUPDATEDELETE 属于当前读,需要读取最新记录并加锁。

这也是理解“RR 是否完全解决幻读”的关键:快照读依靠一致性视图,当前读还会结合记录锁、间隙锁或 Next-Key Lock 控制范围内的并发写入。

锁与索引的关系

InnoDB 的行锁实质上加在索引记录上。更新条件没有合适索引时,可能扫描并锁定大量记录,显著扩大影响范围。间隙锁锁定的是索引区间而不是某条实际记录,主要用于防止范围内插入。

长事务为什么危险

长事务会持续持有锁和旧 Read View,导致 Undo 版本无法及时清理,增加存储和查询成本,也提高死锁与主从延迟风险。事务中不应执行远程调用、等待用户输入等不可控操作。

实战建议

  1. 事务尽量短小,只包围必须保证一致性的数据库操作。
  2. 按固定顺序访问资源,降低死锁概率。
  3. 捕获死锁或锁等待超时后,只对幂等操作进行有限重试。
  4. EXPLAIN 检查索引,避免无谓扩大锁范围。
  5. 明确业务能接受的隔离级别,不要盲目追求最高隔离。

核心考点清单

  • Undo Log 支撑回滚与历史版本,Redo Log 支撑崩溃恢复。
  • Read View 决定版本可见性,RC 和 RR 创建视图的时机不同。
  • 普通 SELECT 常为快照读,FOR UPDATE、UPDATE、DELETE 属于当前读。
  • InnoDB 行锁加在索引记录上,无合适索引可能扩大扫描与锁定范围。

高频追问与参考回答

追问:MVCC 能避免所有锁吗?

不能。它主要优化一致性读;当前读、更新和唯一性检查仍需要锁,DDL 与元数据访问也有自己的协调机制。

追问:RR 下还有幻读吗?

快照读通过一致性视图避免前后结果变化,当前读结合 Next-Key Lock 控制范围插入。混用快照读和当前读时仍需准确分析语义,不能只背“完全解决”。

机制全景图

下面把「MySQL 事务隔离级别与 MVCC 原理」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。

flowchart LR
    A["事务分配标识"]
    A --> B["更新生成新版本与 undo"]
    B --> C["读取建立 Read View"]
    C --> D["沿版本链判断可见性"]
    D --> E["提交后 Purge 清理历史"]

完整链路:从输入到结果

沿着「事务分配标识 → 更新生成新版本与 undo → 读取建立 Read View → 沿版本链判断可见性 → 提交后 Purge 清理历史」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。

1. 事务分配标识

InnoDB 事务开始与首次一致性读的时机受隔离级别影响,Read View 不一定在 begin 时创建。

2. 更新生成新版本与 undo

更新记录写入新值,并通过隐藏事务 ID 与 roll pointer 连接 undo 中的旧版本。

3. 读取建立 Read View

Read View 保存活跃事务范围,用于判断某版本在当前快照是否可见。

4. 沿版本链判断可见性

不可见时沿 undo 版本链回溯;长事务会让链条变长并增加读放大。

5. 提交后 Purge 清理历史

历史版本只有在不再被任何活跃快照需要时才能 Purge,长事务会阻塞回收。

源码与实现定位

入口 阅读重点
storage/innobase/read/read0read.cc Read View 可见性判断
SHOW ENGINE INNODB STATUS History List Length 与事务

源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。

参数配置与可复现实验

SELECT * FROM information_schema.innodb_trx
ORDER BY trx_started;

保持一个长 RR 快照同时持续更新,观察 undo 历史、版本链读耗时和 Purge 恢复。

验证步骤与预期结果

1. 固定输入和基线

先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「最长事务」为主基线,记录值应满足「在线请求秒级」;同时保存 活跃事务时长、History List Length,使后续变化能够回到同一时间轴比较。

2. 从实现入口确认路径

在「storage/innobase/read/read0read.cc」确认请求确实进入「Read View 可见性判断」对应的实现,再沿「SHOW ENGINE INNODB STATUS」观察「History List Length 与事务」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。

3. 注入本文特有的失败模式

优先复现「把快照读误认为完全无锁事务」,并把单一变量逐级放大,直到「最长事务」越过「>60s 示例」。随后再分别验证「长事务阻塞 Purge」和「更新后又把当前读和快照读结果混用」,三类故障分开执行,避免多个变量同时变化而无法归因。

4. 执行止损和根因修复

第一轮只应用「终止异常长事务」,确认它能控制影响范围;第二轮应用「报表拆短批次/副本」,验证核心链路恢复;最后落实「监控 trx_started 和 History List」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。

5. 通过退出条件

实验只有同时满足三项才算通过:「最长事务」回到「在线请求秒级」、「History List」回到「稳态可回落」、「版本链读取」回到「接近当前版本」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。

量化基线

指标 样例基线/口径 风险线 结论
最长事务 在线请求秒级 >60s 示例 长快照
History List 稳态可回落 持续单调涨 Purge 被阻
版本链读取 接近当前版本 读延迟随时间涨 undo 链过长

这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。

事故复盘:报表长事务导致 undo 膨胀

只读报表保持事务数小时,线上更新持续产生 undo,History List Length 不断上升,磁盘和查询延迟恶化。把报表拆成短快照批次并在只读副本执行后,Purge 才能追上。

失败模式 首要证据 第一处置动作
把快照读误认为完全无锁事务 活跃事务时长 终止异常长事务
长事务阻塞 Purge History List Length 报表拆短批次/副本
更新后又把当前读和快照读结果混用 undo 表空间 监控 trx_started 和 History List

发布与回滚检查点

  • 发布前:确认「storage/innobase/read/read0read.cc」对应实现和上述配置在目标版本仍然有效,并保存「最长事务」基线。
  • 灰度中:同时观察 活跃事务时长、History List Length、undo 表空间;任一指标越过表中风险线,就停止继续扩量。
  • 回滚时:先执行「终止异常长事务」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
  • 发布后:至少覆盖一个完整峰值周期,确认「把快照读误认为完全无锁事务」没有再次出现,才关闭变更观察窗口。

方案对比与选型

方案 更适合的场景 主要收益 代价与边界
READ COMMITTED 允许同事务两次读看到新提交 版本链压力较低、语义直观 不可重复读
REPEATABLE READ 事务内需要一致快照 InnoDB 默认且配合 Next-Key Lock 长事务持有旧快照更久
SERIALIZABLE/显式锁读 关键不变量需强串行 语义最强 锁竞争与吞吐成本高

选型至少带上 数据规模、选择性、读写比、事务长度和峰值并发,并用上面的量化基线验证;未知数据应明确为待测假设。

设计边界与工程取舍

MVCC 解决读写并发的版本可见性,不替代唯一约束和业务不变量;写写冲突仍需锁,当前读也会参与锁定。

工程落地遵循:正确性由约束和事务兜底,性能优化必须用执行计划与测量验证。回答时直接引用「storage/innobase/read/read0read.cc」、配置实验和事故数据,比复述固定模板更有说服力。