MySQL事务不直接保证一致性,需配合正确隔离级别、约束机制及应用层校验;autocommit必须关闭才能生效;错误需手动回滚;REPEATABLE READ下幻读仍可能发生,需加锁;外键、CHECK、唯一索引是数据合法性的基础设施;长事务危害大,应避免耗时操作。

MySQL 事务本身不直接“保证”一致性,它只是提供原子性、隔离性、持久性的支撑能力;真正实现业务一致性,得靠你明确事务边界、选对隔离级别、配好约束,再配合应用层的校验逻辑。
START TRANSACTION 后必须显式 COMMIT 或 ROLLBACK
MySQL 默认开启 autocommit=1,单条语句会自动提交——这会让 BEGIN 或 START TRANSACTION 失效。不关 autocommit,事务就形同虚设。
- 执行前先确认:
SELECT @@autocommit;,返回1就得先SET autocommit = 0; - 事务块内任何语句出错(比如违反
NOT NULL或外键),MySQL 不会自动回滚,必须你自己捕获错误后执行ROLLBACK - 忘记
COMMIT会导致连接一直持有锁、undo log 持续增长,可能拖慢整个实例 - 示例中看似简单的转账,如果第二条
UPDATE因账户不存在失败,而你没检查返回值就直接COMMIT,结果就是只扣款、不入账
REPEATABLE READ 不等于“完全防幻读”
InnoDB 的默认隔离级别 REPEATABLE READ 能防止不可重复读,但幻读仍可能发生——尤其在范围查询 + 新增记录的场景下,仅靠 MVCC 不够,得主动加锁。
- 普通
SELECT是快照读,不加锁,无法阻止其他事务插入符合 WHERE 条件的新行 - 需要强一致时,改用
SELECT ... FOR UPDATE(排他锁)或SELECT ... LOCK IN SHARE MODE(共享锁)触发临键锁(Next-Key Lock) - 比如检查“用户当日下单数是否超限”,不能只
SELECT COUNT(*),得SELECT COUNT(*) FROM orders WHERE user_id = 123 AND created_at > '2026-07-01' FOR UPDATE - 注意:
FOR UPDATE在非唯一索引或无索引字段上会升级为表锁,性能影响大
外键、CHECK、唯一约束不是可选项,是事务一致性的基础设施
事务的原子性和隔离性管的是“执行过程”,但数据是否合法、是否符合业务规则,得靠约束来兜底。没有约束的事务,就像没有刹车的车。
-
FOREIGN KEY能防止子表出现孤儿记录,但要注意:InnoDB 外键检查在语句级生效,不是事务级——一条INSERT里插父子记录,父记录没插进去,子记录就会报错 -
CHECK约束(MySQL 8.0.16+ 支持)可强制字段取值范围,比如balance >= 0,比应用层判断更可靠 - 唯一索引不仅能防重复,还能让
INSERT ... ON DUPLICATE KEY UPDATE成为幂等操作,避免并发插入导致脏数据 - 别依赖触发器做核心校验——触发器难调试、难测试,且在批量操作(如
LOAD DATA)中可能被跳过
长事务是数据一致性的隐形杀手
一个运行 5 分钟的事务,不只是慢,它会让 MVCC 快照长期滞留、undo log 无法清理、行锁持续占用,最终引发连锁反应:其他事务等锁超时、复制延迟飙升、甚至主库 OOM。
- 事务内不要做 HTTP 请求、文件读写、复杂计算——这些操作应移到事务外
- 避免在事务中循环执行多条
INSERT/UPDATE,改用批量语句或分批次提交 - 监控
information_schema.INNODB_TRX表,重点关注TRX_STARTED和TRX_ROWS_LOCKED,及时发现异常长事务 - 某些 ORM(如 Django 的
atomic)默认把整个视图函数包进事务,容易无意中拉长事务生命周期
一致性不是开个事务就自动达成的,它藏在隔离级别选择是否匹配业务语义、约束是否覆盖所有非法状态、以及事务粒度是否足够短这几个地方。最容易被忽略的,其实是把本该由数据库兜底的校验逻辑,交给了应用层去“尽力而为”。


















