新手心得:数据库事务到底在保证什么
想象你在手机银行上给朋友转 500 块:你的账户减 500,对方的账户加 500。这两件事必须同进同退——只做一半,钱就会凭空消失或凭空变出来。
数据库里把这样一组“要么全做、要么全不做”的操作打包在一起,就叫一个事务。转账、买票、下单扣库存,背后都是事务在撑着。这套用了几十年的约定,名字叫 ACID。
ACID 是四个英文词的首字母,1983 年由 Härder 和 Reuter 在一篇讲数据库恢复的论文里正式提出,源头是更早的 Jim Gray 对可靠事务系统的描述。四个字母,就是事务要兑现的四句承诺。
第一句是原子性:事务里的操作是一个整体,不可再拆。执行中途出错,数据库会把已经做了一半的修改全部撤销,退回事务开始前的样子,这个动作叫回滚。所以你永远不会看到“钱扣了、对方没收到”这种半截状态。
第二句是一致性:事务执行前后,数据都要符合既定的规则。账户余额不能是负数、订单总价要等于各项之和,这些规则叫约束和业务逻辑。原子性、隔离性、持久性其实都是在为一致性服务。但要注意:数据库只能守住它知道的规则,“该不该转这笔钱”是应用程序的判断。事务能保证数据不被写坏,保证不了业务逻辑本身写错了。
第三句是隔离性:好几个事务同时跑的时候,彼此不能互相捣乱。只剩最后一张票,两个人同时下单,隔离性要保证最终只卖出一张,而不是两人都以为自己抢到了。完全隔离最安全,但代价是大家要排队等,速度变慢。所以数据库一般提供几档隔离级别,从宽松到严格:读未提交、读已提交、可重复读、串行化。级别越低越快,但可能看到别人还没提交的中间结果;级别越高越稳妥,但越慢。选哪一档,是在正确和速度之间取舍,而不是越高越好。
第四句是持久性:事务一旦提交成功,结果就不能再丢。哪怕下一秒服务器断电、硬盘出问题,提交过的数据也必须还在。常用办法是先写日志再落盘:提交之前,先把改动记进一份专门的日志,机器重启后照着日志把没写完的活补上。
工作流程也随之清楚了:开始事务,做一连串读写;都顺利就提交,改动正式生效;中途出错就回滚,当作什么都没发生过。提交和回滚,就是事务的全部开关。
不过也有几个常见误解值得说清楚。
一是“支持 ACID”不等于“万无一失”。不同数据库对 ACID 的实现程度并不一样,隔离级别、日志策略不同,实际保证的强度就有差别。看到宣传语说支持 ACID,先问一句默认是哪一档隔离级别、崩溃后怎么恢复,不能只看四个字母就放心。
二是事务不是越长越好。把几百个操作塞进一个事务里,锁住的数据就多、占用的时间就长,别人只能在外面排队。事务尽量短:只包真正必须同进同退的那几步,做完赶紧提交。
三是单机事务的办法不能直接搬到跨库、跨服务上。一旦改动分布在多个数据库、多个系统里,“要么全做、要么全不做”就难得多,需要另外的协调机制,有些场景只能接受“最终一致”而不是“立刻一致”。后来出现的 BASE 思路——基本可用、软状态、最终一致——和 ACID 正好是两条路线。
说到底,事务解决的是一个朴素的问题:现实里的很多事本来就是“一组动作”,拆开做就会出乱子。ACID 把这组动作绑成一个整体,省去了程序员自己处理断电、并发、半截写入的麻烦。下次遇到“这两个操作必须一起成功”,先想想能不能放进一个事务里。
- 点赞
- 收藏
- 关注作者
评论(0)