1. 事务到底在解决什么问题
先说一个我当年线上真实踩过的坑。那是一个订单支付回调服务,逻辑很简单:用户确认支付后,先更新账户余额,再更新订单状态为已支付。当时代码没加事务,两条 update 各自执行,运气不好时恰好第一条执行完、第二条执行前服务重启了,结果用户余额已经扣了,订单却依然停在“待支付”。用户来投诉,客服去数据库一查,账平不了,还得多赔一堆优惠券才能安抚用户。
这类问题的根源,就是两个数据操作没有绑定成一个 transaction 整体。在 MySQL 这类关系型数据库里,事务能保证一批操作要么全部提交(commit),要么全部回滚(rollback),不会出现“改了一半”的状态。这个“全部成功才算成功,任何一个失败就整体作废”的特性,就是我们常说的事务原子性。
我特别想强调一句:这里讨论的原子性和前端圈讨论的 Atomic CSS 完全是两个世界。Atomic CSS 说的是把样式拆成原子化的工具类,而数据库事务里的原子性,指的是不可拆分的数据操作单元。你要是去面试或者写方案的时候把这两个搞混,会被笑话的。
这篇文章适合三种人看:刚接触数据库事务、写 CRUD 时不太确定怎么切事务边界的初级后端;已经在用 @Transactional 但偶尔遇到回滚不生效、对隔离级别和锁不熟的开发;以及希望系统性地把“本地事务的原子性设计”讲清楚、能直接套用到业务代码里的团队成员。后面所有内容都以 MySQL + Spring 这套最常用的技术栈为例,其他数据库的差异我会顺带说明。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先弄明白一件事:原子性到底保护了什么,不保护什么
事务的原子性,最容易理解,也最容易被误解。你得先知道它精确管到哪一层,后面写代码才不会抱着错误的预期去设计。
2.1 原子性的本质是“一荣俱荣,一损俱损”
我习惯用一个生活化的类比来解释原子性:你带着女朋友去餐厅吃饭,点完菜之后发现忘带钱包,这时候你应该怎么办?正常人的做法是找服务员把还没上的菜退掉,已经上的菜也要想办法结账,不能吃霸王餐跑路。事务里的原子性就是这个道理:不论中途卡在哪个环节,数据库都会主动清理现场,让所有数据恢复到你开事务之前的样子。
数据库层面发生的事比餐厅复杂得多。一条 UPDATE 可能要修改索引,要更新 redo log,要写 undo log,步骤非常多。如果这些步骤做了一半系统突然崩溃,数据库恢复时靠什么知道该把数据还原到哪里?靠的就是 undo log。事务回滚时,InnoDB 会用 undo log 里记录的旧值把数据恢复回去。这是原子性能够成立的底层前提,也是 InnoDB 和 MyISAM 之间一个巨大的差异点:MyISAM 根本不支持事务,也没有 undo log,所以它一条 SQL 写一半崩了,数据就真的坏在那里。
理解了这一层,你就明白一个关键结论:原子性不等于“这个操作过程绝对不会中途失败”,而是“中途失败后系统会把已做的修改全部撤销”。编程时不要把“事务”当做不报错的免死金牌,它只是帮你收拾残局的机制。
2.2 ACID 另外三兄弟与原子性的合作关系
大学课本会说事务有 ACID 四大特性,但我建议你别把它们当成四个孤立的考点,它们其实是在说同一件事的不同侧面。
- 原子性(Atomicity):一组操作不可分割,要么全都成功,要么全都像没发生过。
- 一致性(Consistency):事务开始前和结束后,数据都要满足业务上的约束,不能出现“转账后总金额变少”这种离谱状态。
- 隔离性(Isolation):两个事务同时操作同一批数据时,不能互相干扰到产生错误结果。
- 持久性(Durability):事务一旦提交,修改就要永久保留下来,即使数据库立刻宕机,重启后数据也在。
一致性最容易和原子性混在一起。我的理解是:一致性是目标,原子性、隔离性、持久性是达成目标的手段。比如转账场景,要求“A扣100块、B加100块”这件事前后总账不变。如果不用事务,A 扣了 B 没加,账就不平了,这是一致性被破坏。事务把扣款和加分绑定成一个原子操作,避免了一半成功的情况;同时配合行锁保证并发时不会有人看到中间状态。所以你会发现,想保住一致性,往往不仅要靠原子性,还要靠隔离级别和锁,甚至还要靠数据库的约束、触发器之类的东西。
这里必须泼一盆冷水:原子性并不是什么灵丹妙药,它没有“并发安全”“数据不超卖”这种额外的能力。两个事务同时扣同一个账户的余额,如果没有锁或版本号控制,即使它们各自是原子操作,最后也可能把余额扣成负数。并发控制是隔离性那一侧的工作,后面我会专门说明。
3. 保证原子性的基础配置:引擎、连接、事务边界
既然事务能力取决于底层引擎和连接状态,那么第一步不是急着写代码,而是确认你的表真的具备支持事务的前提。这一步我见过太多次坑了。
3.1 存储引擎没选对,写了事务也白搭
MySQL 有多个存储引擎,默认的 InnoDB 支持完整的事务能力,老旧的 MyISAM 则完全不支持。在生产环境里,只要用了 MyISAM,那么你代码写的 @Transactional、SQL 里写的 BEGIN/COMMIT,统统都会失效。MyS
