每次看到数据库里那堆 SQL,我都会想,到底是提交后才真正生效,还是得等回滚才能撤销?其实在实际项目里,commit 与 rollback 不是玄学,而是一次性把改动写进表的关键动作。今天就带着这份实战经验,帮大家把“提交”这两个字拆得明明白白。

最常见的写法其实就两行代码:begin tran;update users set score=score+1 where id=10;commit;这组语句把对 id=10 的记录打分加一,然后把修改永久写入表。如果不加 commit,多数数据库默认处于事务模式,直到你手动提交或关闭连接。这里的关键在于把所有改动集中在一次提交里,避免中间状态被意外看到。
想象一下,你要在订单表里同时扣减库存、记录操作日志、更新用户余额。此时,先扣库存、写日志,再检查余额是否足够,如果发现不足,就需要把前面的扣减全部撤销。这时候,把所有语句包在同一个事务里,一次 commit,才能让这三件事要么全成功,要么全回滚。一次性提交的优势在于,数据库保证了这些操作的原子性,不会出现半账导致的不一致。
回滚的场景往往在发现错误时才会触发。比如在扣减库存后,发现余额不足,这时可以执行 rollback,让数据恢复到事务开始前的状态。回滚的时机需要精准,最好在业务逻辑的检查点执行。很多新手会把 rollback 放在 finally 块里,以确保即使出现异常也能恢复,这种做法在生产环境里非常实用。
但更精细的业务场景里,往往需要“部分回滚”或“选择性提交”。比如在处理批量订单时,前十条记录更新成功,第十一条因为数据校验失败而抛出异常,这时候如果整体 rollback,前面十条的努力就白费了。更合理的做法是,在事务内部设置保存点(savepoint),把每一条记录的更新视为一个独立步骤,一旦某一步出错,只需回滚到最近的保存点,再继续处理后续逻辑。这样既保证了整批操作的连贯性,又让异常处理变得可控。
另一个常被忽略的细节是事务的隔离级别。默认情况下,多数数据库使用“读已提交”级别,这意味着在事务未提交前,其他会话看不到你修改的数据,但如果你在事务里先查询了某条记录,再基于查询结果做更新,就可能遇到“不可重复读”的问题。比如,你在事务里先算出用户的总积分,然后去更新等级,期间另一个会话恰好改了积分,你的等级判定就会基于过期数据。这时,把隔离级别提升到“可重复读”或使用行级锁,能避免这类竞态条件。实战中,我会根据业务对一致性的敏感度,灵活调整隔离级别,而不是一刀切。
还有一点值得注意:commit 和 rollback 不只是数据库层面的操作,它们还影响到连接池的状态。在高并发环境下,如果事务迟迟不提交,连接会被长期占用,导致连接池耗尽,进而拖垮整个应用。我曾经处理过一个线上事故,某个定时任务在循环里开启事务,却忘记在循环外提交,结果每个迭代都积累未提交的改动,最终连接池爆掉,服务全面瘫痪。那次教训让我养成了习惯:凡是涉及事务的代码,一定要在 finally 块里检查事务状态,确保连接能及时归还。
最后,回到最初的问题——commit 与 rollback 并非玄学,而是业务逻辑与数据库机制之间的桥梁。它们像一对默契的搭档,一个负责永久定格,一个负责时光倒流,而你要做的,就是根据业务需求,在正确的时间点做出正确的选择。记住,事务的边界就是业务的边界,每一次提交都是一次承诺,每一次回滚都是一次修正。把这两件事想透了,写起数据操作来,自然会心里有底,手里有数。


