logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔4
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置
前端编程3
前端基础知识16
UniApp15
Vue框架19
Java编程7
Java基础知识14
SpringBoot31
SpringMVC14
MyBatis8
SpringCloud15
RocketMQ1
MyCat1
数据库4
MySQL6
Redis7
MongoDB10
H21
Java技术栈4
知识整理分布式锁的实现方案MySQL事务MySQL的锁
软件工具箱14
IDEAGitMavenGradleNginx安装Nginx配置JMeter压测OllamaPicGoRustFSVSCodeDockerObsidianObs录制
Linux知识11
树莓派安装及使用ArchLinux:常用软件ArchLinux:基础系统安装ArchLinux:深度优化ArchLInux:图形化界面安装ArchLinux:Nirifrp内网穿透Jar启动脚本Linux常用命令VirtualBox安装CentOSVirtualBox安装Ubuntu
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

一、ACID 特性

特性 含义 实现机制
原子性 Atomicity 要么全成功,要么全失败 undo log(回滚日志)
一致性 Consistency 事务前后数据满足所有约束 其他三个特性共同保证
隔离性 Isolation 并发事务互不干扰 MVCC + 锁
持久性 Durability 提交后永久保存 redo log(重做日志)

注意:一致性是"目标",ACI 是"手段"。面试时不要说"一致性靠 XX 实现"。

二、隔离级别

四种级别

级别 脏读 不可重复读 幻读
READ UNCOMMITTED(读未提交)
READ COMMITTED(读已提交)
REPEATABLE READ(可重复读) ⚠️
SERIALIZABLE(串行化)

InnoDB 默认 RR,其他数据库(Oracle、PostgreSQL)默认 RC。

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

-- 初始数据:id=1, balance=100
脏读 不可重复读 幻读
现象 读到其他事务未提交的修改 同一事务两次读到不同的值 同一事务两次读到不同的行数
示例 A 改 100→200 未提交,B 读到 200 A 第一次读 100,B 改后提交,A 第二次读 200 A 第一次查 5 行,B 插入一行提交,A 第二次查 6 行
触发操作 UPDATE(读未提交数据) UPDATE(数据变了) INSERT/DELETE(行数变了)

一句话区分:脏读是读到别人没提交的,不可重复读是同一行值变了,幻读是行数变了。

RR 级别的幻读问题

InnoDB 的 RR 通过 MVCC + Next-Key Lock 大部分解决了幻读,但仍有特殊情况:

-- 事务 A
SELECT * FROM t WHERE id > 10;          -- 快照读,结果 2 行
-- 事务 B 插入 id=15 并提交
SELECT * FROM t WHERE id > 10;          -- 快照读,仍 2 行(MVCC 起作用)
UPDATE t SET v = 1 WHERE id = 15;       -- 更新了 B 刚插入的行!
SELECT * FROM t WHERE id > 10;          -- 结果 3 行(幻读出现!)

原因:UPDATE 是当前读,触发了最新版本的更新,打破了快照的一致性。

三、MVCC 原理

MVCC(Multi-Version Concurrency Control)让 InnoDB 实现读不加锁、读写不冲突

三个隐藏字段

每行记录都有:

字段 大小 作用
DB_TRX_ID 6 字节 最近修改这行的事务 ID
DB_ROLL_PTR 7 字节 回滚指针,指向 undo log
DB_ROW_ID 6 字节 行 ID(无主键时用)

undo log 版本链

当前记录 → [trx_id=300, roll_ptr][trx_id=200, roll_ptr][trx_id=100, roll_ptr] → 初始版本

每次 UPDATE 不直接覆盖,而是在 undo log 中记录旧版本,形成链式版本。

ReadView

事务在快照读时创建一个 ReadView,包含四个关键信息:

可见性判断(从版本链最新往前找):

  1. trx_id == creator_trx_id → 可见(自己改的)
  2. trx_id < min_trx_id → 可见(修改已提交)
  3. trx_id >= max_trx_id → 不可见(修改在 ReadView 之后产生)
  4. trx_id in m_ids → 不可见(修改未提交),不在则可见

RC 与 RR 的 ReadView 差异

RC RR
时机 每次 SELECT 生成新 ReadView 事务内第一次 SELECT 生成,后续复用
效果 能看到其他事务已提交的修改 事务内一直看到相同快照

这就是为什么 RC 会有不可重复读而 RR 不会

四、redo log 与 undo log

redo log(重做日志)

undo log(回滚日志)

WAL(Write-Ahead Log)

redo log 的核心思想:先写日志再写磁盘,避免每次修改都随机写磁盘。

修改数据 → 写到 Buffer Pool → 写 redo log → 后台刷脏页 → 返回提交成功

崩溃恢复时重放 redo log 即可找回未刷脏页的数据。

五、事务的启动方式

显式事务

START TRANSACTION;          -- 或 BEGIN
-- DML 操作
COMMIT;                     -- 或 ROLLBACK

隐式事务

SET autocommit = ON;        -- 每条语句自动提交(MySQL 默认)

链式事务

SET autocommit = 0;         -- 关闭自动提交,需手动 COMMIT

建议:长事务用 SET autocommit = 0 前确保及时提交,避免锁长期不释放。

六、长事务的危害与处理

危害

  1. undo log 膨胀:长事务意味着旧版本无法回收
  2. 锁长时间不释放:阻塞其他事务
  3. 主从延迟:大事务提交瞬间写大量 binlog

排查

-- 查找持续时间超过 60 秒的事务
SELECT * FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;

避免

  1. SET autocommit = ON(推荐,避免忘记提交)
  2. 拆分大事务为小事务
  3. 避免在事务中做 RPC 调用等耗时操作

七、常见面试题

  1. ACID 分别怎么实现的? → 原子性 undo log,一致性 ACI 共同保证,隔离性 MVCC+锁,持久性 redo log

  2. RR 和 RC 区别? → RR 解决不可重复读(ReadView 复用),有间隙锁;RC 每次新 ReadView,无间隙锁

  3. 快照读和当前读? → 普通 SELECT 快照读(MVCC 无锁),FOR UPDATE/UPDATE/DELETE/INSERT 当前读(加锁)

  4. MVCC 能完全解决幻读吗? → 不能,当前读场景仍可能幻读,需配合 Next-Key Lock

  5. redo log 和 binlog 区别? → redo log 是 InnoDB 引擎层,物理日志,循环写;binlog 是 Server 层,逻辑日志,追加写

  6. 两阶段提交是什么? → redo log 先 prepare → 写 binlog → redo log commit,保证两者一致性

  7. 为什么 RR 没有完全解决幻读却被设为默认级别? → MVCC + Next-Key Lock 解决了绝大多数场景;配合 statement-based binlog 时 RR 比 RC 更安全

一、ACID 特性
二、隔离级别
四种级别
脏读、不可重复读、幻读的区别
RR 级别的幻读问题
三、MVCC 原理
三个隐藏字段
undo log 版本链
ReadView
RC 与 RR 的 ReadView 差异
四、redo log 与 undo log
redo log(重做日志)
undo log(回滚日志)
WAL(Write-Ahead Log)
五、事务的启动方式
显式事务
隐式事务
链式事务
六、长事务的危害与处理
危害
排查
避免
七、常见面试题