背景
在我们的印象中,事务具有原子性,一致性,隔离性,持久性,以为什么操作只要封装在一个事务中就是安全的,事实果真如此吗?
技术背景
我们用Mysql数据库作为例子,并使用最常用的事务隔离级别:读提交或可重复读作为数据库的默认事务隔离级别,我们以扣减用户账号的余额作为例子,正常情况下用户的余额不能为负数,现在账号A正在同时发起对外的两笔转账记录,假设账号A当前余额为10000元,同时向B和C转账8000元,按我们理解正常情况下是用户A余额是不足的,但是如果我们使用如下事务实现:你猜结果会怎么样?
begin transaction;
select money from account where user=A;
if(money > 8000){
update account set money = money -8000 where user=A;
update account set money = money +8000 where user=B;
}
commit;
假设在事务并发的情况下,给账号B和账号C转账时,同时判断if(money > 8000)这个条件都是成立的,这意味着最终的结果是用户B和用户C都收到了8000元的转账金额,而用户A的账号变成了-6000元,这明显是有问题的,if(money>8000)一开始的目的就是为了防止用户A的钱变成负数,也就是判断A至少有足够的用于转账的钱,现在明显看到并没能达到目的,那这里就有疑问了,即使使用事务也是不能保证读取-判断-写入模式是安全的吗?
结论
答案:是的,即使使用事务也是不能保证读取-判断-写入模式是安全的,那怎么做呢?可以使用以下的方法,对读取记录加锁,
begin transaction;
select money from account where user=A for update;
if(money > 8000){
update account set money = money -8000 where user=A;
update account set money = money +8000 where user=B;
}
commit;
这种情况下,当账号A对B转账时,会对A的账号加锁,这样同时进行的账号A对C转账时,会因为获取不到A账号的锁而等待,所以这样就可以确保账号A对B转账完成后,账号A对账号C的转账才会继续下去,其实这样和把数据库的事务隔离级别设置成可串行化是类似的。都能达到一样的效果