MySQL事务必须满足ACID四约束才算真正可用:原子性靠undo log实现全成或全败;一致性依赖约束与事务协同保障业务规则;隔离性由MVCC与锁机制在REPEATABLE READ等级别下平衡并发与性能;持久性通过redo log刷盘确保提交即落地。

MySQL 事务不是“有事务功能”就完事了,它必须满足 ACID 四个硬性约束才算真正可用。不支持 ACID 的所谓“事务”,比如 MyISAM 引擎的 BEGIN/COMMIT,只是语法上像,实际没原子性、没回滚、没隔离——等于裸奔。
原子性(Atomicity):要么全成,要么白干,中间状态不能留
这是事务最基础的底线。比如转账:扣 A 账户、加 B 账户,这两步必须捆在一起。如果扣款成功但加款失败,数据库绝不能只留下“A 少了钱、B 没多钱”的残缺状态。
- 靠
undo log实现:每条UPDATE/INSERT/DELETE都先记下“怎么反向撤销”,出错时直接按 log 倒带 -
START TRANSACTION后,任何语句报错(如主键冲突、字段超长、违反CHECK约束),只要没COMMIT,整个事务自动标记为需回滚 - 常见坑:
INSERT IGNORE或ON DUPLICATE KEY UPDATE看似“容错”,但它们属于单条语句级处理,不改变事务整体原子性逻辑;真要保原子,得靠外层事务 + 异常捕获 - 注意:只有
InnoDB支持完整原子性;MyISAM不写undo log,ROLLBACK无效
一致性(Consistency):不是数据库自己“觉得一致”,而是你定义的规则必须被强制执行
一致性不是事务“带来”的,而是事务 + 约束 + 触发器 + 应用逻辑共同守住的红线。事务只是那个“守门人”:它确保约束检查发生在事务提交前,且失败即回滚。
- 典型体现:
FOREIGN KEY级联、CHECK(balance >= 0)、唯一索引冲突、触发器抛异常——这些都会让COMMIT失败 - 容易忽略的点:一致性是**业务语义层面**的。比如银行总余额不变,这不是数据库自动保证的,而是靠你在事务里显式校验(如
SELECT SUM(balance)前后比对)或靠应用逻辑兜底 -
SET FOREIGN_KEY_CHECKS = 0会绕过外键检查,等于主动放弃一部分一致性保障,上线前务必确认是否真需要
隔离性(Isolation):并发时别互相“串味”,但 MySQL 默认不完全隔离
InnoDB 默认隔离级别是 REPEATABLE READ,但它**不等于**串行执行。幻读(Phantom Read)在当前读(SELECT ... FOR UPDATE)下仍可能发生,只是快照读(普通 SELECT)被 MVCC 隐藏了。
- 四个级别差异关键在“读可见性”:
READ UNCOMMITTED可见未提交修改(脏读);READ COMMITTED每次SELECT都读最新已提交版本(不可重复读);REPEATABLE READ事务内所有快照读都基于首次读的时间点;SERIALIZABLE加范围锁,彻底串行 - 实战建议:高并发写场景慎用
SERIALIZABLE,性能损耗大;REPEATABLE READ下若需防幻读,必须显式加SELECT ... FOR UPDATE或用INSERT ... ON DUPLICATE KEY UPDATE替代“先查再插” - 一个经典陷阱:
UPDATE t SET x = x + 1 WHERE id = 1在READ COMMITTED和REPEATABLE READ下行为一致,但UPDATE ... WHERE name = 'xxx'(无索引)可能锁全表,导致严重阻塞
持久性(Durability):提交即落地,断电也不能丢
一旦 COMMIT 返回成功,数据就算服务器立刻断电、进程崩溃,重启后也必须可恢复——这靠的是 redo log 刷盘机制,不是直接写数据文件。
-
innodb_flush_log_at_trx_commit是核心开关:1(默认)= 每次提交都刷盘,最安全;0= 每秒刷一次,崩溃最多丢 1 秒事务;2= 写 OS 缓存但不强制刷盘,依赖 OS 稳定性 - 别被“磁盘写入”误导:
redo log是顺序 I/O,远快于随机写数据页;所以即使sync_binlog=1开启,只要innodb_flush_log_at_trx_commit=1,持久性就有保障 - 真实风险点:某些云厂商 RDS 默认调低该参数以换性能,或使用机械盘 + 高并发写导致
fsync成瓶颈,此时COMMIT延迟飙升,应用可能超时误判失败
ACID 不是抽象概念,每个字母背后都是日志策略、锁机制、约束检查和刷盘控制的具体实现。真正踩过 undo log 被提前 purge 导致回滚失败、redo log 空间满引发 hang、或者 REPEATABLE READ 下幻读引发重复插入的坑,才会明白——事务的“可靠”,从来不是开个 BEGIN 就自动附送的。










