Theory · MySQL · InnoDB

事务与 ACID

一组 SQL 的原子契约 —— 每个字母背后都是一套机制:undo、redo、MVCC 与锁

ACID → 实现映射

原子性靠 undo log 回滚、持久性靠 redo log + 双"1"配置、隔离性靠 MVCC + 锁——一致性是前三者的结果

并发三异常

脏读 / 不可重复读 / 幻读:SQL 标准按"是否禁止异常"划隔离级别,与 MVCC 的机制口径互补

语法与工程

autocommit、START TRANSACTION、SAVEPOINT、隐式提交清单、长事务识别——面试与生产都在这里翻车

这份 deck 是 MySQL 事务的全景入口。它和另外两份 deck 分工明确:MVCC 的版本链机制、锁的加锁规则分别有专门 deck 深讲,这里负责事务视角的主干——ACID 各由什么保证、三异常怎么定义、隔离级别矩阵、2PL、语法层细节和长事务治理。面试里事务题往往从 ACID 开场,五分钟内会被引到 undo/redo、MVCC 或锁,所以交叉链接就是你的追问地图。

ACID · §17.2

ACID 四性逐条:每一性都指着一套机制

A · 原子性 Atomicity

事务内操作全做或全不做:失败即回滚到事务前状态。保证者:undo log——回滚时按反向逻辑逆操作(insert→delete、update→反向 update)。提交后想"撤销历史"也是它(闪回思想的根基)。

I · 隔离性 Isolation

并发事务互不观察对方的中间状态。保证者:MVCC(快照读)+ 锁(当前读/写写)组合拳,强度由隔离级别选择。机制细节见 mvcclock-internals

D · 持久性 Durability

一旦 commit,断电也不丢。保证者:redo log(WAL)——先顺序写日志再落数据页;配合 innodb_flush_log_at_trx_commitsync_binlog 双 1 配置、doublewrite buffer、binlog 两阶段提交。细节见 log-redo-undo-binlog

C · 一致性 Consistency

事务把数据库从一个合法状态带到另一个合法状态:约束(主键/唯一/外键)、应用层业务规则不被破坏。它是 A、I、D 三者协同的结果而非独立机制——手册把 doublewrite/崩溃恢复也归入此处防护"内部处理损坏"。

高频追问"ACID 分别由什么保证"的标准答案:A→undo log;D→redo log + 两阶段提交;I→MVCC + 锁;C→AID 共同作用 + 约束与业务语义。一句"ACID 里只有 C 是目的,AID 是手段"可直接收尾。
ACID 开场题的答法是把每个字母直接落到机制上。原子性是 undo log 做逻辑逆操作;持久性是 redo log 加 WAL,生产上还要双 1 配置兜底;隔离性是 MVCC 和锁的组合,强度由隔离级别选;一致性最容易被答虚——它是前三者加上约束和业务语义的结果,只有 C 是目的,A、I、D 是手段,这句总结能让面试官停一下。手册 17.2 页把每个字母的 related features 列得很清楚,复习时可以对照。

Mapping Table

一张表串起 MySQL 的日志、版本与锁

性质机制做什么深讲在哪
原子性undo log(回滚段)记录逻辑逆操作;ROLLBACK 时逆放;insert undo 提交即弃、update undo 服务版本链mvcc · undo 版本链 / log-redo-undo-binlog
持久性redo log(WAL)+ binlog 2PCcommit 先落 redo(prepare)→ 写 binlog → redo 提交;崩溃恢复按 redo 重放、按 undo 回滚未提交log-redo-undo-binlog
隔离性(读)MVCC:隐藏列 + undo 版本链 + ReadView快照读不加锁,沿版本链挑可见版本;RC/RR 只差视图建立时机mvcc 全 deck
隔离性(写)行锁:Record / Gap / Next-Key + 意向锁写写串行化;当前读防幻读靠 next-key;表级先走意向锁与 MDLlock-internals 全 deck
一致性以上全部 + 约束/恢复主键唯一外键约束、崩溃恢复(redo 前滚 + undo 回滚)、doublewrite 防页撕裂本 deck 第 2 页 · log-redo-undo-binlog
面试展开顺序建议:先给这张映射表(30 秒),面试官指哪行就深入哪行——问原子性讲 undo 的两类日志,问持久性讲 redo 与 binlog 的两阶段提交,问隔离性就顺势进 MVCC 四步判定或锁的加锁规则。一张表把追问主动权拿到手。
这页是整个 MySQL 复习体系的枢纽表。它的价值不是内容深,而是结构清晰:面试官问 ACID,你先花半分钟把这张表说全,然后主动把话题引向你最熟的分支。每行右侧都标了深讲位置——原子性和持久性去日志那份 deck,隔离性读去 MVCC、写去锁那份。注意一致性这行的表述:约束、崩溃恢复、doublewrite 都是为内部一致服务,业务语义的一致是应用层的事,这句区分能防杠。

Concurrency Anomalies

并发三异常:SQL 标准的"定义口径"

异常定义(标准口径)对比维度一句话记法
脏读
dirty read
读到其他事务未提交的数据;若对方回滚,你基于"从未存在"的数据做了决策数据是否提交读到了"不存在过"的值
不可重复读
non-repeatable read
同一事务内,同一行两次读取值不同(他人 UPDATE/DELETE 已提交)同一行的一行读两次,值变了
幻读
phantom
同一事务内,同一条件两次读取返回的行集合不同(他人 INSERT/DELETE 已提交)符合条件 的行集合多出 / 少了一"批"行

与 mvcc deck 的机制口径互补

mvcc 从实现角度回答"RR 下幻读解决了吗"(快照读复用 ReadView / 当前读靠 next-key lock)。本页是标准定义口径:异常是"现象分类",机制是"如何消除"——先现象后机制,答题层次立刻清晰。

口径陷阱:UPDATE 也算幻读的一部分

SQL 标准里幻读的成因包含 INSERT 和 DELETE/UPDATE 改变行集合归属——"同一行两次读值不同"归不可重复读,"符合条件的行集合变化"归幻读,两者可能由同一 UPDATE 触发,按现象归类而不是按语句归类。

三异常的定义要抠字眼。脏读的关键是未提交且可能回滚;不可重复读针对同一行的值;幻读针对符合条件行集合的变化。最容易混淆的是后两者,记住对比维度:一个看值,一个看集合。还有一个口径细节能加分:幻读不只由 insert 引起,update 和 delete 让行进入或退出条件集合也算幻读,按现象归类而不是按语句归类。这页和 mvcc deck 是互补关系,那边讲机制这边讲定义。

Walkthrough

时间线推演:三个异常各一遍

脏读、不可重复读、幻读的时间线推演 三条泳道各演示一个异常:脏读是 T2 未提交即被 T1 读到随后回滚;不可重复读是 T2 提交更新后 T1 两次读同一行值不同;幻读是 T2 提交插入后 T1 两次按同条件读行集合不同。 脏读 Dirty Read —— 读到未提交数据(所有隔离级别都禁止) 时间 → T2 T1 UPDATE bal=800(未提交) SELECT → 800 ✗ 脏 ROLLBACK 800 这个值从未存在过 T1 却已经基于它做了决策 → 所以最低级别也必须禁止 不可重复读 Non-repeatable Read —— 同一行两次读,值不同 时间 → T1 T2 SELECT bal → 1000 UPDATE 800 · COMMIT SELECT → 800 ✗ 变了 同一事务 · 同一行 · 两次读 值 1000 → 800:报表对账、 分段计算类逻辑会被它打穿 RC 允许 / RR 禁止 幻读 Phantom —— 同一条件两次读,行集合不同 时间 → T1 T2 SELECT count(*) WHERE age>18 → 10 INSERT 新行 · COMMIT 同条件 → 11 ✗ 多了一行 与不可重复读的区分维度: 不可重复读看"一行的值" 幻读看"集合的基数" InnoDB 消除路径见 mvcc deck ✗ = 该次读违反本级别期望 · 三个异常按"现象"归类,不按触发语句归类
三条泳道就是三段白板戏。脏读那条强调结局:T2 回滚后,T1 读到的 800 从未存在过,所以哪怕最低级别也不允许。不可重复读那条强调场景:对账和分段计算会被打穿。幻读那条强调区分维度:一个看一行的值,一个看集合的基数。每条泳道右边的蓝框是给面试官的补充说明,画完时间线再指一下,层次就有了。RR 和 RC 为什么表现不同,机制答案在 mvcc deck。

Isolation Matrix

四级别异常矩阵:标准怎么定,InnoDB 怎么超

隔离级别脏读不可重复读幻读InnoDB 实现要点(机制深讲见 mvcc
READ UNCOMMITTED允许允许允许无视图保证,行为像 RC 但不一致——生产禁用
READ COMMITTED禁止允许允许每条快照读新建 ReadView;gap 锁禁用(外键/重复键检查除外);UPDATE 半一致读;只支持 row binlog
REPEATABLE READ(InnoDB 默认)禁止禁止标准允许
InnoDB 基本消除
首次快照读建 ReadView 全程复用;当前读用 next-key lock 挡插入——比标准 RR 更强
SERIALIZABLE禁止禁止禁止autocommit=0 时普通 SELECT 隐式转 FOR SHARE,读也加锁

标准矩阵 vs InnoDB 的两点超纲

① 标准只要求 RR 禁止脏读+不可重复读,允许幻读;InnoDB 的 RR 通过 MVCC + next-key lock 把幻读"基本消除"(快照读不会、当前读被阻断、混用有一条缝)。② 标准没规定默认级别,各库自选:MySQL 选 RR,PostgreSQL 选 RC。

为什么 RC 只支持 row binlog?

RC 下 gap 锁禁用、半一致读提前放行,语句的加锁范围与执行顺序不受控,statement 格式重放到从库可能得到不同结果——手册直接规定 READ COMMITTED "only row-based binary logging is supported"(MIXED 自动转 row)。

矩阵表按列读:从左到右异常逐级被禁。这页的重点是两个超纲点:第一,标准的 RR 允许幻读,InnoDB 做得比标准更严,用 MVCC 加 next-key lock 基本消除;第二,标准不规定默认级别,MySQL 选了 RR,PostgreSQL 选 RC,这个对比是下一页的主角。RC 只支持 row binlog 的原因要能讲出来:加锁范围不受控导致 statement 重放结果不确定。矩阵里每个单元格都能被追问,所以每格右边都留了机制索引。

Why REPEATABLE READ

InnoDB 为什么默认 RR:一句历史 + 一套机制账

历史账:statement-based 复制安全

早年 MySQL 默认 binlog 是 statement 格式:从库重放 SQL 语句本身。这要求语句在主库执行时"锁住它读到的范围"直到提交,重放结果才确定——RR + next-key lock 恰好提供这个保证;RC 禁 gap 锁、加半一致读,语句结果不可复现(MySQL Bug #23051 即此问题的官方记录)。

现状账:8.0 默认已是 row binlog

8.0 默认 binlog_format=ROW,语句重放安全的约束已松绑——但默认级别保持 RR(兼容与语义惯性)。若切 RC,必须确认 binlog 是 row/MIXED;不少互联网公司仍选 RC 换更低锁冲突(见 QA 15)。

对比项SQL 标准 RRInnoDB RR
脏读 / 不可重复读禁止 / 禁止禁止 / 禁止(ReadView 复用)
幻读允许(标准只管"读已提交"语义的行级一致)快照读不会(视图复用);当前读被 next-key lock 阻断;混用两类读留一条缝(mvcc · 幻读页
实现自由度标准只定义现象边界,不规定实现MVCC(快照读无锁)+ 2PL(当前读/写锁)组合,强于标准
答题口径:"InnoDB 默认 RR 有历史原因——statement 复制需要语句级可复现,RR 的 next-key lock 提供这个保证;如今 8.0 默认 row binlog 后约束解除,但 RR 作为默认保留,且它的实现强度超过标准 RR——快照读加临键锁把幻读基本消除。"
为什么默认 RR 是一道区分度很高的题。标准答案分两层:历史层,早年默认 statement binlog,从库重放语句要求主库把读到的范围锁到提交,RR 加 next-key lock 正好做到,RC 做不到所以官方 bug 库里专门有记录;现状层,8.0 默认 row binlog 之后这个约束已经解除,默认保留 RR 更多是兼容惯性。答完还可以补一句横向对比:PostgreSQL 默认 RC,因为它没有 statement 复制的历史包袱。这个话题想深入就顺到锁那份 deck。

Two-Phase Locking

两阶段锁:锁跟着语句走,释放跟着 commit 走

概念内容
2PL 协议事务分两阶段:增长段只加锁不放锁,收缩段只放锁不加锁。满足 2PL 的调度可串行化——隔离性的经典理论基础
S2PL(Strict 2PL)更严格:写锁(实践中读锁也)一直持有到事务结束才释放。现代数据库普遍采用——避免级联回滚(读不到别人未提交的写)
InnoDB 的事实行为DML 与锁定读的行锁在 commit / rollback 时才释放。手册 §17.7.2.4 原话:"All locks set by FOR SHARE and FOR UPDATE queries are released when the transaction is committed or rolled back."——正是 S2PL
与 MVCC 的分工快照读不取锁(走 ReadView,见 mvcc);当前读与写写冲突走 2PL(见 lock-internals)。"隔离性 = MVCC + 2PL" 是完整表述

对死锁的影响

锁窗口 = 事务时长。两阶段特性意味着事务中途无法提前放锁来"绕开"死锁——只能靠缩小事务、统一加锁顺序、合理索引来降低概率;真发生则靠 InnoDB 死锁检测回滚受害者(lock-internals · 死锁页)。

对吞吐的影响

热点行的持锁时间直接决定并发度:事务里做 RPC、慢查询、等待人工输入,等于把行锁窗口拉长几十倍——这就是"事务要小"的协议层根据。隔离级别选 RC 也是在放宽锁窗口(gap 锁禁用、半一致读)。

2PL 这页要讲三件事。定义:增长段只加不放、收缩段只放不加,S2PL 更严格,锁到事务结束才释放——InnoDB 正是这么做的,手册原话可以直接背。影响一:死锁不能靠中途放锁规避,只能靠小事务和加锁顺序,真死锁交给检测器。影响二:吞吐,锁窗口等于事务时长,事务里做 RPC 就是把锁窗口放大几十倍,这是所有事务治理建议的协议层根源。最后点一句分工:快照读不参与 2PL,它走 MVCC。

Syntax 1/3 · §15.3.1

语法层 ①:autocommit 与事务的打开方式

-- autocommit=1(默认):每条语句原子
-- 手册:as if it were surrounded by
-- START TRANSACTION and COMMIT
SET autocommit = 0;   -- 之后须显式 COMMIT/ROLLBACK

-- 推荐的显式事务(可带修饰符)
START TRANSACTION
  [READ WRITE | READ ONLY]
  [WITH CONSISTENT SNAPSHOT];

-- 结束方式
COMMIT [AND [NO] CHAIN] [[NO] RELEASE];
ROLLBACK;              -- 同样支持 CHAIN/RELEASE
要点说明
autocommit=1每语句即事务;出错时该语句自动回滚,但已提交语句不回滚
BEGIN / BEGIN WORKSTART TRANSACTION 别名;存储程序内 BEGIN 被解析为 BEGIN…END 块,必须用 START TRANSACTION
READ ONLY手册:"MySQL enables extra optimizations for queries on InnoDB tables when the transaction is known to be read-only"——只读事务跳过 trx id 分配等开销(§10.5.3)
AND CHAIN / RELEASECHAIN:提交后立刻以相同特性开新事务;RELEASE:提交后断开会话
WITH CONSISTENT SNAPSHOT:手册——"The effect is the same as issuing a START TRANSACTION followed by a SELECT from any InnoDB table",即把 ReadView 的建立提前到事务开始(默认要等第一次快照读)。仅 RR 有意义:"For all other isolation levels, the WITH CONSISTENT SNAPSHOT clause is ignored"。
语法层第一页。autocommit 默认开启,手册的说法是每条语句仿佛被 START TRANSACTION 和 COMMIT 包着,出错时该语句自动回滚。BEGIN 是别名,但存储过程里会被解析成 BEGIN END 块,必须用 START TRANSACTION,这个细节面试偶尔考。READ ONLY 修饰符不只是声明,InnoDB 对只读事务有专门优化,跳过事务 id 分配。最重要的修饰符是 WITH CONSISTENT SNAPSHOT:把 ReadView 建立提前到事务开始,而且只在 RR 下有效,其他级别直接忽略并给警告——这句话把语法和 mvcc 机制串起来了。

Syntax 2/3 · §15.3.4 / §15.3.3

语法层 ②:SAVEPOINT 与"会隐式提交"的语句清单

SAVEPOINT:事务内的部分回滚

SAVEPOINT sp1;
-- ...
ROLLBACK TO sp1;   -- 回到 sp1,事务未结束
-- 外层 ROLLBACK 仍可整体回滚
RELEASE SAVEPOINT sp1;

语义:ROLLBACK TO 撤销 sp1 之后的行级变更、保留更早的;sp1 仍存在可再次 ROLLBACK TO;同名 SAVEPOINT 新建会覆盖旧的;COMMIT / 整体 ROLLBACK 清除全部 savepoint。典型用途:批量导入分批设点、失败从最近 checkpoint 续跑。

隐式提交清单(§15.3.3)——事务里碰到即"翻车"

类别代表语句
DDL(执行前后各提交一次CREATE/ALTER/DROP TABLE、CREATE INDEX、RENAME/TRUNCATE TABLE、CREATE/ALTER/DROP VIEW、存储过程/触发器
账号与权限CREATE/DROP/ALTER USER、GRANT、REVOKE、SET PASSWORD
事务控制(仅执行前提交)BEGIN、START TRANSACTION、SET autocommit=1、LOCK TABLES
管理与维护ANALYZE/CHECK/OPTIMIZE/REPAIR TABLE、FLUSH、LOAD INDEX…
复制控制START/STOP REPLICA、CHANGE REPLICATION SOURCE TO

例外:CREATE/DROP TEMPORARY TABLE 不触发隐式提交,但也不可回滚——手册明说"causes transactional atomicity to be violated"。

事故链:长事务未提交 → 会话 A 跑 ALTER TABLE → 需要 MDL 排他锁 → 被长事务的 MDL 共享锁阻塞 → 后续所有该表读写排队 → 整库雪崩。解法与推演见 lock-internals · MDL 页;快照失效报 ER_TABLE_DEF_CHANGED 见 mvcc · 边界页
这页两块内容。左边 SAVEPOINT:部分回滚后事务并没有结束,外层仍然可以整体回滚,存储点也还在,这几个语义细节是追问点。右边隐式提交清单,务必记住三类:DDL 前后各提交一次,账号权限语句,以及 BEGIN 这类事务控制语句会先提交当前事务。临时表是例外,不触发隐式提交但也不可回滚。底部的 MDL 事故链是这页的杀手锏:事务里跑 DDL 等于隐式提交加 MDL 排他锁,配合长事务能阻塞整张表的读写,具体推演在锁那份 deck。

Syntax 3/3 · SET TRANSACTION

语法层 ③:隔离级别的设置语句与作用域

-- 语法:SET [GLOBAL | SESSION] TRANSACTION
--       ISOLATION LEVEL {RU | RC | RR | SERIALIZABLE}
--       [, READ ONLY | READ WRITE]

SET SESSION TRANSACTION ISOLATION LEVEL
  READ COMMITTED;      -- 当前会话后续事务

SET GLOBAL TRANSACTION ISOLATION LEVEL
  REPEATABLE READ;     -- 之后新建的会话

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 不带作用域 = 只影响"下一个事务"(one-shot)

-- 8.0 亦可直接设系统变量(注意是连字符)
SET GLOBAL transaction_isolation = 'read-committed';
作用域影响范围
GLOBAL之后新建的会话;已有会话不变
SESSION当前会话后续所有事务
无关键字下一个事务——连接池里要小心"一次性设置"漂移
命名坑:SET TRANSACTION 用空格(READ COMMITTED);系统变量用连字符('read-committed')。变量 transaction_isolation 自 5.7.20 起取代 tx_isolation。改 GLOBAL 不影响已有连接——线上误配"以为生效"的经典来源。

修改前提:若已设 read_only,切换隔离级别需要相应权限(§15.3.6)

设置隔离级别有两条路:SET TRANSACTION 语句和 transaction_isolation 系统变量。三个作用域必须分清:GLOBAL 只影响之后新建的会话,SESSION 影响当前会话的后续事务,不带关键字只影响下一个事务。两个坑:一是命名风格,语句用空格变量用连字符;二是线上改 GLOBAL 不影响已有连接,连接池长连接会让配置"看起来没生效"。用连接池的服务里,one-shot 设置还会随连接复用漂移,这句生产感很值钱。

Long Transactions

长事务:识别、危害与治理(业务侧视角)

危害链条

① 连接与锁窗口被拉长:行锁 / MDL 长时间持有 → 阻塞与死锁概率上升(lock-internals);② MVCC 侧钉住 update undo → History list length 上涨 → undo 膨胀、版本链变长(机制详见 mvcc · purge 与长事务);③ 复制延迟放大:大事务在从库单线程重放。

识别抓手

information_schema.INNODB_TRX:trx_started(起始时间)、trx_state、trx_rows_modified——查 trx_started < NOW() - INTERVAL 60 SECONDSHOW ENGINE INNODB STATUS 的 History list length 看 purge 积压;performance_schema.events_transactions_current 补位。

业务侧治理具体做法
事务里不做"慢东西"禁止 RPC/HTTP 调用、大计算、等人工确认;把事务收敛为"取数→内存决策→短事务落库"
大批量写分批提交10 万行 DELETE 拆成小批循环,避免超长 undo 与主从延迟;每批独立事务 + 间隔
应用层兜底统一事务模板:begin 后 defer rollback,成功才 commit(防 panic 挂连接);DB 侧配合 innodb_lock_wait_timeout 与 kill 长事务脚本
监控告警对 INNODB_TRX 时长、History list length、trx_rows_modified 设阈值告警——事后复盘靠 binlog,事前预警靠这三件
面试表述:"长事务是所有机制的公共放大器——它同时拉长锁窗口、钉住 undo、放大复制延迟;治理思路是让事务只包含必要的 DB 操作,其余移出事务边界。"
长事务是 mvcc 和锁两份 deck 的公共出口,这页从业务侧收口。危害讲三条:锁窗口、undo 膨胀、复制延迟,每条都能指向对应 deck 的深讲页。识别上 INNODB_TRX 是主抓手, trx_started 和 trx_rows_modified 两个列最好背下来,History list length 看 purge 积压。治理部分是工程师的日常:事务里不做 RPC 和慢计算,大批量分批提交,代码里统一事务模板防 panic 漏提交。最后那句话术可以背:长事务是所有机制的公共放大器。

Interview QA · 1/2

原理 8 连问

1 · ACID 分别由什么保证?

undoredo+2PCMVCC+锁

A→undo log 逆操作回滚;D→redo log(WAL)+ redo/binlog 两阶段提交;I→MVCC(快照读)+ 锁(当前读);C→AID 协同 + 约束 + 业务语义。补一句"只有 C 是目的,AID 是手段"。

2 · 一致性和前三者什么关系?约束算吗?

结果而非机制

数据库侧的一致性 = 原子性 + 隔离性 + 持久性共同保证执行正确,再加主键/唯一/外键等约束与崩溃恢复;业务侧一致性(如转账两边同变)由应用逻辑配合事务完成,不是存储引擎单方面给的。

3 · 脏读 / 不可重复读 / 幻读的区别?

未提交一行值行集合

脏读:读到未提交且可能回滚的数据;不可重复读:同一行两次读值不同(UPDATE/DELETE);幻读:同条件两次读行集合不同(INSERT/DELETE 进入或退出条件)。对比维度:值 vs 集合基数。

4 · RR 下幻读解决了吗?

两分答法

快照读:解决——全程复用 ReadView,新插入行不可见;当前读:靠 next-key lock 阻断插入;缝在混用:看不见的行 UPDATE 改得到。完整推演见 mvcc · 幻读页,本 deck 提供标准定义口径。

5 · InnoDB 为什么默认 RR 不是 RC?

SBR 历史row binlog 现状

历史:statement binlog 需要语句级可复现,RR+next-key lock 保证,RC 禁 gap 锁不可保证(Bug #23051)。现状:8.0 默认 row binlog,约束解除但默认保留;RR 实现强于标准 RR。PostgreSQL 默认 RC 可作横向对比。

6 · 2PL 是什么?锁什么时候释放?

增长/收缩两段S2PL 到 commit

2PL:事务分加锁增长段与放锁收缩段,可串行化的理论基础;S2PL 要求写锁持有到事务结束。InnoDB:FOR SHARE/FOR UPDATE 与 DML 的行锁都在 commit/rollback 才释放(§17.7.2.4 原话)。后果:锁窗口=事务时长,死锁无法靠中途放锁规避。

7 · 事务里能跑 DDL 吗?

隐式提交MDL 排他

能跑但代价大:DDL 执行前后各做一次隐式提交(§15.3.3),之前的事务被静默提交;同时 DDL 要 MDL 排他锁,被长事务阻塞会引发整表排队。临时表 DDL 是例外:不隐式提交但也不可回滚。

8 · WITH CONSISTENT SNAPSHOT 干什么用?

提前建 ReadView仅 RR 有效

默认 RR 的视图在第一次快照读才建立;该修饰符把建立时点提前到事务开始(手册:等价于 START TRANSACTION 后立刻 SELECT 一次)。其他隔离级别下被忽略并给 warning。

QA 第一组是原理主干。第一二题连着答,用 ACID 映射表加"只有 C 是目的"收尾。第三题抠定义字眼。第四题给两分答法并主动说完整推演在 mvcc deck。第五题分历史层和现状层,能提到 PostgreSQL 默认 RC 就是加分项。第六题背手册那句话,锁到 commit 才释放。第七题把隐式提交和 MDL 事故链带出来。第八题一句话:把 ReadView 建立提前,仅 RR 有效。

Interview QA · 2/2

语法与工程 8 连问

9 · ROLLBACK TO SAVEPOINT 之后事务是什么状态?

未结束点仍在

部分回滚:撤销 savepoint 之后的行变更,事务仍活跃、可继续提交或整体回滚;该 savepoint 仍存在可重复使用,更早的 savepoint 不受影响。注意行锁不因 ROLLBACK TO 提前释放——仍在 2PL 框架内到 commit 才放。

10 · 哪些语句会隐式提交?

DDL 前后账号权限BEGIN 之前

DDL(CREATE/ALTER/DROP/RENAME/TRUNCATE TABLE、CREATE INDEX…)前后各一次;CREATE/ALTER/DROP USER、GRANT/REVOKE;BEGIN/START TRANSACTION/SET autocommit=1/LOCK TABLES 仅执行前提交;ANALYZE/OPTIMIZE/FLUSH 等管理语句。临时表 DDL 例外。

11 · 长事务怎么发现?有什么危害?

INNODB_TRXHistory list

发现:information_schema.INNODB_TRX 的 trx_started / trx_rows_modified;SHOW ENGINE INNODB STATUS 的 History list length。危害:锁与 MDL 窗口拉长、update undo 无法 purge 致 undo 膨胀、从库重放大事务延迟。治理:慢操作移出事务、分批提交。

12 · START TRANSACTION READ ONLY 有什么用?

优化声明语义保护

两层:语义上写操作被拒绝(TEMPORARY 表 DML 除外);性能上手册明说 InnoDB 对已知只读事务启用额外优化——只读事务可不分配 trx id、不计入 purge 判定(§10.5.3)。报表/对账场景值得显式声明。

13 · SET TRANSACTION 的三种作用域?

GLOBAL/SESSION/下一事务

GLOBAL 只影响之后新建会话(已有连接不变);SESSION 影响当前会话后续事务;不带作用域只影响下一个事务。坑:语句值用空格、系统变量 transaction_isolation 用连字符;改 GLOBAL 后长连接不生效。

14 · Go 服务里怎么管事务边界?

模板化不跨 RPC

统一封装:Begin 后 defer Rollback(提交后置标志防误回滚),成功路径才 Commit;事务内禁止 RPC/慢调用;用 context 超时联动避免连接被挂死;批量写分批提交。本质是把 2PL 的锁窗口压到最小。

15 · 生产环境 RC 还是 RR?

看锁冲突与语义需求

RC:gap 锁禁用 + 半一致读 → 锁冲突与死锁更少,互联网公司常用,但必须 row binlog 且应用要接受"读最新已提交";RR:跨语句一致读(报表/对账)与可重复语义。前提检查:binlog_format 与团队对隔离语义的依赖。

16 · 一条 UPDATE 提交失败回滚,各组件都做了什么?

串题收束

写路径:改 buffer pool 页 + 记 undo(可回滚)+ 记 redo(prepare)→ 写 binlog → redo commit;回滚:按 undo 逆操作恢复行与二级索引(delete-mark 记录等 purge 清理)。追问出口:undo/redo 细节见 log-redo-undo-binlog,可见性与 purge 见 mvcc。

QA 第二组偏语法与工程。第九题的陷阱是以为部分回滚会释放行锁,其实还在 2PL 框架里到 commit 才放。第十题把清单说全。第十二题 READ ONLY 是冷门加分题,能说出跳过 trx id 分配说明读过 10.5.3。第十三题两个坑都要点出来。第十四题结合 Go 讲事务模板和 context 超时联动,这是后端面试的连接点。第十六题是串题收束,把 undo、redo、binlog 2PC 一条线讲完,主动把追问引向你准备好的 deck。

Related Decks

相关知识点

本领域相关 deck

mvcc.html · 隔离性的读侧机制:ReadView / 版本链 / 幻读 / purge
lock-internals.html · 隔离性的写侧机制:锁层级 / MDL / 死锁
log-redo-undo-binlog.html · 原子性与持久性的载体:undo / redo / binlog 2PC
replication.html · RR 默认与 binlog 格式的历史因果在复制的落地
index-btree.html · 加锁与回表都发生在索引结构上

答题串联 · 一图流

ACID → undo / redo+2PC / MVCC+锁 / 约束
三异常 → 脏读(未提交) · 值(一行) · 集合(一批行)
级别 → RU·RC·RR·SERA 矩阵 + InnoDB 超纲点
2PL → 锁窗口 = 事务时长 → 小事务的一切理由
语法 → autocommit · CONSISTENT SNAPSHOT · 隐式提交清单

收尾第一页给出四个方向的深讲链接和答题串联逻辑:先背映射表和三异常定义,再把追问出口接到 mvcc、锁、日志三份 deck 上,事务这条线就完整了。参考出处单独放下一页。

References

参考来源

本 deck 全部结论可溯源至下列一手材料

dev.mysql.com/doc/refman/8.0/en/mysql-acid.html§17.2 InnoDB and the ACID Model:各字母的 related features 清单(autocommit/COMMIT/ROLLBACK、isolation level、doublewrite、innodb_flush_log_at_trx_commit)
dev.mysql.com/doc/refman/8.0/en/commit.html§15.3.1:autocommit 语义、START TRANSACTION 修饰符、WITH CONSISTENT SNAPSHOT 等价语义与非 RR 忽略、READ ONLY 优化、CHAIN/RELEASE
dev.mysql.com/doc/refman/8.0/en/savepoint.html§15.3.4:SAVEPOINT / ROLLBACK TO SAVEPOINT / RELEASE SAVEPOINT 语义
dev.mysql.com/doc/refman/8.0/en/implicit-commit.html§15.3.3:DDL 前后隐式提交、账号权限语句、事务控制语句仅前提交、临时表例外(atomicity violated)
dev.mysql.com/doc/refman/8.0/en/set-transaction.html§15.3.6:GLOBAL/SESSION/one-shot 作用域与 transaction_characteristics 语法
dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html§17.7.2.1:四级别行为、InnoDB 默认 RR、RC 半一致读与仅 row binlog、SERIALIZABLE 隐式 FOR SHARE
dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html§17.7.2.4:"All locks set by FOR SHARE and FOR UPDATE queries are released when the transaction is committed or rolled back"(S2PL 行为依据)· NOWAIT/SKIP LOCKED
dev.mysql.com/doc/refman/8.0/en/information-schema-innodb-trx-table.htmlINNODB_TRX 列(trx_started / trx_state / trx_rows_modified)——长事务识别
percona.com/blog/mysql-performance-implications-of-innodb-isolation-modes/ · bugs.mysql.com/bug.php?id=23051默认 RR 与 statement 复制安全的历史因果;RC 破坏 SBR 的官方记录
这份 deck 的版本敏感结论都有出处:ACID 映射出自 17.2,语法细节出自 15.3.1、15.3.3、15.3.4、15.3.6,级别矩阵和默认值出自 17.7.2.1,S2PL 行为依据是 17.7.2.4 的原话,默认 RR 的历史因果引用了 Percona 的分析和官方 bug 记录。