5.1.1 原⼦性

MySQL 原子性(Atomicity)完全指南

原子性是 ACID 的基石。如果说事务是一枚硬币,原子性就是硬币不可分割的物理实体------它确保"全有或全无"(All or Nothing),杜绝"半成品"状态的存在


一、原子性的本质定义

原子性(Atomicity) 指的是:一个事务中的所有操作,在逻辑上是一个不可分割的最小工作单元 。这些操作要么全部执行成功 (提交),要么在遇到任何错误时全部撤销(回滚),绝不允许出现"执行了一部分"的中间状态。

生活中的类比

取款机取钱:扣卡余额 + 吐出现金。如果钱扣了但机器不出钞,银行必须把钱退回去。这个"退回"动作,就是原子性的体现。

SQL 中的直观体现

sql 复制代码
-- 转账:扣钱 + 加钱 必须同生共死
START TRANSACTION;

UPDATE account SET balance = balance - 100 WHERE user_id = 1;  -- 扣钱
UPDATE account SET balance = balance + 100 WHERE user_id = 2;  -- 加钱

-- 如果第二条SQL失败(如账户冻结),第一条必须撤销
COMMIT;  -- 或 ROLLBACK;

二、原子性的底层实现原理:Undo Log

原子性由 Undo Log(回滚日志)独家保障。与 Redo Log 负责"重做"(持久性)不同,Undo Log 专职负责"撤回"(原子性)。

2.1 Undo Log 是什么?

Undo Log 是 InnoDB 存储引擎为每个读写事务生成的历史版本数据链 。在执行任何修改操作(INSERT/UPDATE/DELETE)之前,InnoDB 会先将修改前的旧数据以逻辑日志的形式记录下来。

存储位置 :Undo Log 存储在 Undo 表空间undo_001undo_002)中,默认存放在系统表空间(ibdata1)或独立的 undo 文件中。

2.2 不同操作对应的 Undo 记录

操作类型 Undo Log 记录内容 回滚时的逆向操作
INSERT 记录新行的主键值(PK) 执行 DELETE,根据主键删除新行
DELETE 记录整行的旧数据副本 执行 INSERT,将旧数据原样插回
UPDATE 记录被修改字段的旧值 执行 UPDATE,将字段改回旧值
UPDATE(主键) 记录旧行数据 + 新行主键 拆分为 DELETE 旧行 + INSERT 新行

关键设计差异:INSERT 的 Undo 只记主键(行太新,无需存全量);DELETE 和 UPDATE 必须存完整旧数据,因为要精确复原。

2.3 InnoDB 行记录的隐藏字段

InnoDB 中每行记录(聚簇索引叶子节点)都带有 3 个隐藏字段,它们是 Undo Log 链的核心:

隐藏字段 作用
DB_TRX_ID 最后修改该行的事务 ID
DB_ROLL_PTR(回滚指针) 指向 Undo Log 中该行历史版本的指针,形成版本链
DB_ROW_ID 行 ID(无主键时用于聚簇索引)

版本链示意图

复制代码
当前行(最新版本)
├── DB_TRX_ID = 100
├── DB_ROLL_PTR ──────┐
│                      ▼
│              ┌─────────────────┐
│              │  Undo Log V2    │  ← UPDATE 前的旧值 (trx_id=99)
│              │  DB_ROLL_PTR ───┼──┐
│              └─────────────────┘  │
│                                   ▼
│                           ┌─────────────────┐
│                           │  Undo Log V1    │  ← INSERT 的原始值 (trx_id=98)
│                           │  DB_ROLL_PTR = NULL
│                           └─────────────────┘

三、事务执行全流程中的原子性保障

3.1 正常提交(COMMIT)流程

复制代码
START TRANSACTION;
  │
  ├─① 分配事务ID (trx_id = 100)
  │
  ├─② 执行 UPDATE ... (修改数据页)
  │   ├── 将修改前的旧数据写入 Undo Log (记录 trx_id=100)
  │   ├── 修改 Buffer Pool 中的数据行
  │   └── 将新行的 DB_TRX_ID 设为 100,DB_ROLL_PTR 指向新 Undo
  │
  ├─③ 执行 INSERT ... (插入新行)
  │   └── 将新行的主键写入 Undo Log
  │
  ├─④ 写入 Redo Log (保证持久性,与原子性无关)
  │
  └─⑤ COMMIT
       └── 在 Redo Log 中写入 COMMIT 标记
           事务成功,Undo Log 进入"待清理"状态(但保留用于 MVCC)

3.2 异常回滚(ROLLBACK)流程

当发生以下情况时,触发回滚:

  • 应用层显式执行 ROLLBACK
  • SQL 执行报错(如违反唯一约束、字段溢出)
  • 死锁被检测到,MySQL 自动回滚受害者事务
  • 锁等待超时(innodb_lock_wait_timeout
  • 数据库宕机重启后的恢复阶段

回滚执行过程

复制代码
触发 ROLLBACK / 异常中断
  │
  ├─① 从当前行的 DB_ROLL_PTR 开始,遍历该事务的所有 Undo Log 记录
  │
  ├─② 按照 Undo Log 的逆向顺序执行回滚:
  │   ├── 遇到 INSERT Undo → 执行 DELETE (根据主键删新行)
  │   ├── 遇到 UPDATE Undo → 执行 UPDATE (将字段改回旧值)
  │   └── 遇到 DELETE Undo → 执行 INSERT (将旧行插回)
  │
  ├─③ 将事务状态标记为 TRX_UNDO_PREPARED → ROLLBACK_DONE
  │
  └─④ 释放该事务持有的所有锁
      数据完全恢复至事务开始前的状态。

3.3 崩溃恢复时的原子性保障

MySQL 宕机重启时,会通过 Redo Log + Undo Log 协作进行恢复:

事务状态 恢复策略 实现方式
已提交(COMMIT) 前滚(Redo) 应用 Redo Log,重做已提交的修改
未提交 / 已回滚 后滚(Undo) 应用 Undo Log,撤销未完成事务的修改

这个过程确保了即使数据库在事务执行中途宕机,重启后也不会留下"半截子"数据。


四、容易被忽略的原子性陷阱

4.1 事务中的 DDL 操作(隐式提交)

在 MySQL 中,DDL 语句(CREATE、ALTER、DROP、TRUNCATE)会触发隐式提交,导致当前事务被强制提交,无法回滚。

sql 复制代码
START TRANSACTION;

INSERT INTO users (name) VALUES ('Alice');  -- 可回滚

ALTER TABLE users ADD COLUMN age INT;        -- ❗ 触发隐式 COMMIT

ROLLBACK;  -- 此时只能回滚到 ALTER 之后,Alice 已被永久插入!

强制规范绝对禁止在业务事务中混入 DDL 操作。DDL 应单独执行。

4.2 嵌套事务的误解

MySQL 不支持真正的嵌套事务(SAVEPOINT 除外)。

sql 复制代码
START TRANSACTION;  -- 事务 A

UPDATE t SET x=1;

START TRANSACTION;  -- 实际是隐式 COMMIT 了事务 A,新启事务 B
UPDATE t SET x=2;

ROLLBACK;  -- 只回滚事务 B,x 变成了 1(事务 A 已提交)

4.3 部分回滚(SAVEPOINT)

MySQL 支持通过 SAVEPOINT 实现事务内的部分回滚,但不影响整体原子性逻辑:

sql 复制代码
START TRANSACTION;

INSERT INTO t VALUES (1);
SAVEPOINT sp1;

INSERT INTO t VALUES (2);  -- 假设这里出错
ROLLBACK TO SAVEPOINT sp1; -- 只撤销第二条插入

INSERT INTO t VALUES (3);
COMMIT;  -- 最终插入 1 和 3,(2) 被撤销。整体原子性依然成立。

4.4 回滚失败的可能性

理论上,Undo Log 回滚也可能失败(如磁盘损坏)。但 InnoDB 的设计保证了:

  • Undo Log 本身也受 Redo Log 保护,保证其完整性
  • 如果回滚过程中发生错误,InnoDB 会进入崩溃恢复模式,禁止用户访问,直到问题解决
  • 绝大多数生产环境中的回滚都是成功的

五、原子性 vs 持久性(Undo vs Redo)

这两个概念常被混淆,对比理解更清晰:

对比维度 原子性(Atomicity) 持久性(Durability)
核心问题 事务失败时,如何撤销已做的修改? 事务成功后,如何防止修改丢失?
实现日志 Undo Log(回滚日志) Redo Log(重做日志)
存储内容 修改前的旧数据(逆向操作) 修改后的新数据(正向操作)
应用时机 事务回滚 时 / 崩溃恢复(后滚) 事务提交 时 / 崩溃恢复(前滚)
日志类型 逻辑日志(记录逆向 SQL 逻辑) 物理日志(记录数据页的物理修改)
空间释放 Purge 线程异步回收(需确保无 MVCC 读依赖) 循环写入(覆盖),空间固定

六、原子性对 MVCC 的额外贡献

Undo Log 不仅是回滚工具,还是 MVCC(多版本并发控制) 的数据源。

  • REPEATABLE READ 隔离级别下,事务开始时生成 Read View
  • 读取数据时,通过 DB_ROLL_PTR 沿着 Undo 版本链向后遍历,找到对当前事务可见的历史版本
  • 即使事务提交后,Undo 记录也不能立即物理删除,因为可能有其他长事务还在读取该历史快照
  • 由后台 Purge 线程负责判断:当没有任何 Read View 再需要某个 Undo 版本时,才将其物理清理

七、生产环境最佳实践与监控

✅ 利用原子性的正确姿势

  1. 事务尽量短小:长事务会产生大量 Undo Log,占用磁盘空间,且影响 Purge 效率
  2. 显式控制边界 :始终使用 START TRANSACTION / BEGIN,避免使用 autocommit=1(默认)导致每句都成事务
  3. 捕获异常并回滚 :应用层务必 try-catch,在 catch 中执行 ROLLBACK
  4. 监控 Undo 表空间大小:Undo 膨胀可能导致磁盘写满,定期监控

📊 监控原子性相关指标

sql 复制代码
-- 1. 查看当前运行中的事务(判断是否有长事务产生大量 Undo)
SELECT trx_id, trx_state, trx_started, 
       trx_rows_locked, trx_rows_modified,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec
FROM information_schema.innodb_trx
ORDER BY trx_started;

-- 2. 查看 InnoDB 状态中的 Undo 信息(包括 Purge 进度)
SHOW ENGINE INNODB STATUS\G
-- 重点查看 "TRANSACTIONS" 部分:
-- "History list length" 表示等待 Purge 的 Undo 记录数量,数值过大说明 Purge 跟不上

-- 3. 查看 Undo 表空间大小(MySQL 8.0)
SELECT TABLESPACE_NAME, FILE_NAME, TOTAL_EXTENTS, FREE_EXTENTS
FROM information_schema.INNODB_TABLESPACES
WHERE TABLESPACE_NAME LIKE 'undo%';

⚠️ 常见问题排查

现象 可能原因 解决方案
Undo 表空间无限增大 存在长时间未提交的读事务(如慢查询、备份),导致 Purge 无法清理 查找并终止长事务,优化查询
History list length 持续飙升 写入压力大,Purge 线程处理不及时 调大 innodb_purge_threads,检查是否有大事务
回滚操作极其缓慢 单个事务修改了海量数据(数千万行) 拆分大事务为批量小事务,避免一次性修改过多数据

八、总结

核心要点 关键描述
本质定义 事务内的操作是一个不可分割的原子单位:全成功或全失败
实现技术 Undo Log(回滚日志),记录修改前的旧数据(逆向操作)
回滚机制 通过 DB_ROLL_PTR 遍历 Undo 版本链,逆向执行反向 SQL 恢复数据
额外价值 MVCC 提供历史版本数据,支撑一致性非锁定读
空间管理 Purge 线程异步清理不再需要的 Undo 记录
最大威胁 大事务/长事务产生海量 Undo,导致磁盘爆满和性能雪崩

一句话记住原子性

原子性是事务的"后悔药",通过 Undo Log 记录所有修改前的旧模样,确保无论发生什么意外,数据都能一键复原到起点。

在生产环境中,"短小精悍" 是事务设计的黄金法则------事务越小,原子性保障的成本越低,系统越健壮。

相关推荐
Pocker_Spades_A几秒前
时序数据库选型指南:从大数据视角看Apache IoTDB的工业级优势
数据库
梦想平凡7 分钟前
情怀棋牌源代码焕新记录(一):全新国风UI与原版工程梳理
java·服务器·数据库·websocket·网络协议·cocos2d
cspttty8 分钟前
FP&A应届岗技能路线:Excel建模、SQL取数、BI看板与预算分析
大数据·数据库
微学AI27 分钟前
飞牛 NAS 部署 prompts.chat:自建提示词库、导入社区内容,再配置固定公网访问
数据库·内网穿透
羑悻的小杀马特29 分钟前
从写 YAML 到集群自愈:KES-Operator 把 KES 集群接进 Kubernetes 原生体系
运维·数据库·容器·kubernetes
bksczm33 分钟前
MySQL基础篇之事务
linux·数据库·sql·mysql
李白客34 分钟前
数据库管理工具怎么选?Navicat、DataGrip、DBeaver 六维横向对比与选型建议
运维·数据库
马立杰35 分钟前
mysql中Illegal mix of collations for operation “UNION”错误的解决方法
数据库·sql·网络安全
蓝速科技37 分钟前
蓝速 K10 三防平板在工业恶劣工况下的选型与应用指南
大数据·运维·数据库·人工智能·科技
bksczm39 分钟前
MySQL基础篇之视图与用户管理
linux·数据库·sql·mysql