你有没有遇到过这种情况?公司用的订单系统,两个人同时下单修改同一件商品库存,结果数据对不上,查来查去发现记录“错乱”了。其实这背后,很可能就是数据库事务冲突惹的祸。
事务冲突是怎么发生的?
在数据库里,事务是保证数据一致性的基本单位。比如转账操作,扣钱和收款必须同时成功或失败。但当多个事务同时访问同一数据时,就可能产生冲突。最常见的就是“写-写冲突”——两个人同时修改同一条记录,谁的修改生效?还有“读-写冲突”,比如一边读取余额,另一边正在扣款,读出来的可能是错误中间值。
怎么检测这些冲突?
现代数据库常用一种叫“多版本并发控制”(MVCC)的技术。简单说,就是每次修改数据时,不直接覆盖旧值,而是保留历史版本。每个事务看到的是它开始那一刻的“快照”。如果两个事务试图修改同一个版本的数据,系统就会检测到冲突,强制其中一个回滚。
举个例子,在 PostgreSQL 中,如果你开启两个事务同时更新同一行:
BEGIN;<br>UPDATE accounts SET balance = balance - 100 WHERE id = 1;<br>-- 此时另一个会话也执行相同语句<br>COMMIT;后提交的那个事务可能会收到“could not serialize access due to concurrent update”的提示,这就是冲突被检测到了。
乐观锁 vs 悲观锁
处理冲突有两种思路。悲观锁像“先占坑”,一上来就加锁不让别人动,适合写操作频繁的场景。而乐观锁更像“事后检查”,允许大家先改,提交时再判断有没有冲突,适合读多写少的情况。很多Web应用用的就是乐观锁,在表里加一个 version 字段,每次更新都比对版本号。
比如你在电商后台修改商品信息,页面加载时带上了 version=5。保存时如果数据库里这条记录已经被别人改成 version=6 了,你的更新就会被拒绝,避免覆盖别人的修改。
怎么减少冲突影响?
事务尽量短小精悍,别在一个事务里做一堆耗时操作。比如别在事务里发邮件、调外部接口。另外,合理设计索引也能减少锁的范围。没有索引时,数据库可能得锁住一大片数据,增加了撞车概率。
有时候业务上也可以妥协。比如抢购活动,不需要强一致性,可以用最终一致性方案,把请求排队异步处理,既保护数据库,又提升响应速度。