浅谈 SQL 中的锁(一)余额问题

举一个用户余额的例子,余额可以是现金、虚拟币、积分等,可以看作对数据准确度要求高的数据,一旦出错,就会造成客户或公司的直接损失。

在 SQL Server 建立一个测试表:

--测试用余额表
create table balance
(
	id int primary key,	--用户ID
	value int			--余额
)

--插入测试数据
delete balance
insert balance values(1, 1)


ID 为 1 的用户有 1 块钱的余额

执行一个【假如用户的余额超过或等于 1 元,则扣除 1 元】的操作:

--检查用户的余额是否足够
if(exists(select * from balance 
          where id = 1 and value >= 1))
begin --延长处理时间
	waitfor delay '0:00:10'

	--更新余额
	update balance set value = value - 1
end

--查看处理结果
select * from balance


在 10 秒之内,另开一个连接,再次执行上面的操作。这是一个并发的操作,在语句模拟的处理时间之内执行。

第二次操作之后,发现用户的余额变成了 -1,这种情况是不能容忍的。

出现这个问题的原因是,在两个并发的操作中,检查余额的语句执行的时候,用户的余额都是足够的,因此两个扣钱的操作都执行了。

从技术的角度上来说,就是两个操作并没有互斥,本来第二个操作应该在第一个操作完成扣钱之后,再去查询余额的。

其实数据库是可以做这样的互斥的,只不过这样做会牺牲性能,于是大部分数据库的隔离级别是【读取提交(READ COMMITTED)】,查询语句也是使用的共享锁。

默认的选择是一种适合大多数情况的,平衡的做法。但显然不适合这种计算余额的情况。

事务型数据库早就想到了这一点,只要在 SQL 语句做一点修改,就可以解决这个问题。

粤ICP备15047625号-2