检查约束是否线程安全?能否替代悲观锁?跨数据库行为一致吗?
能否用检查约束替代悲观锁?
核心结论
检查约束绝对不能替代悲观锁解决余额并发修改的问题,两者的设计目标和作用场景完全不是一回事。
为什么检查约束搞不定并发问题?
检查约束的本质是保证数据符合预设规则(比如余额不能为负),但它只在执行INSERT/UPDATE语句的那一瞬间做验证,属于语句级的静态校验,根本管不了并发场景里的“中间操作冲突”。
举个实际场景:假设用户余额是100,两个线程同时要扣80:
- 线程A先读余额是100,算出扣完剩20
- 线程B同时也读到余额100,同样算出剩20
- 两个线程的UPDATE语句都能通过检查约束(最终余额20≥0),执行完后余额变成20,但实际应该是100-80-80=-60(如果不允许负余额,其中一个操作必须失败)
这种“超扣”的问题,检查约束完全拦不住,因为它看不到其他线程的中间操作过程。
正确解决余额并发修改的方案
针对这类需要强一致性的场景,靠谱的做法有这几种:
- 悲观锁:读余额时直接加锁,比如SQL Server里用
SELECT balance FROM Balances WHERE userId = @id WITH (UPDLOCK, HOLDLOCK),确保同一时间只有一个线程能改这条记录 - 乐观锁:给表加个
version字段,更新时带版本号判断:UPDATE Balances SET balance = balance - 80, version = version + 1 WHERE userId = @id AND version = @currentVersion,如果更新行数为0,说明有并发冲突,重试就行 - 原子更新语句:直接在UPDATE里计算余额,避免先读后写:
UPDATE Balances SET balance = balance - 80 WHERE userId = @id AND balance >= 80,靠数据库的行锁机制保证操作原子性
不同数据库中检查约束的行为差异
主流关系型数据库的检查约束核心功能一致——验证数据合法性,但细节有区别:
- SQL Server:检查约束不能包含子查询,只能基于当前行的字段值做校验
- PostgreSQL:支持更灵活的检查约束,允许用子查询(比如限制用户总余额不超全局上限),但照样解决不了并发冲突问题
- MySQL:5.7版本之前,检查约束只是语法上支持,实际不生效;5.7及以后才真正执行校验
不管哪种数据库,检查约束都没能力处理并发冲突,它只能保证数据的静态合法性,管不了并发操作下的数据一致性。
内容的提问来源于stack exchange,提问作者user19291301
相关产品推荐
相关产品推荐

