您好,同学!先看一下完整的SQL语句
update account set balance=balance+CAST(? AS DECIMAL(30,6)) where account_no=? and balance>=-1*CAST(? AS DECIMAL(30,6))
通常我们更新数据库的时候是这样执行的:
1. 开启一个数据库事务。
2. 查询出需要更新的数据
select account_no,balance from account where account_no=?
3. 计算新的balance值:
balance=balance-扣减金额
4. 执行更新语句
update account set balance=? where account_no=?
5. 提交事务。
只需要指定需要更新的数据和数据库表唯一标识字段更新即可,那么这样带来的问题就是在高并发情况下运行时,会导致balance被额外扣减,如下表格:
从表格中可以看到,更新后的balance和预期不一样,并发问题导致剩余金额错误,那其中一种解决方法是使用数据库行锁,也就是把上面的第2步修改一下,添加for update:
select account_no,balance from account where account_no=? for update
那么只有拿到锁的事务才可以执行,就把2个事务强制串行化,那么剩余金额就不会出现错误了,过程就变成了:
这个方法本身没问题,但使用锁将执行串行化,增加了数据库负担降低了性能,这里的锁就是悲观锁,那么就采用乐观锁来解决问题,可以再看一下开头的那个SQL:
update account set balance=balance+ 扣减的值 where account_no=? and balance>=-1*扣减的值
目的是扣减的计算逻辑从代码移动到update语句吗,update语句执行时数据库本身会保证原子性,
同时,在where语句中除了执行唯一标识,还加了一个限制 and balance>=-1*扣减的值,含义就是,如果更新的数据中如果 balance>=-1*扣减的值,剩余金额大于扣减金额,才可以扣减;如果剩余金额小于扣减金额,就无法扣减,update语句也就会不会实际执行了。
至于cast函数,是把传进来的数字转换成对应的数据库表字段类型,才能正确执行,所以最终的只需要执行一个update SQL就可以了:
update account set balance=balance+CAST(? AS DECIMAL(30,6)) where account_no=? and balance>=-1*CAST(? AS DECIMAL(30,6))
另外同学可以异步学习另外的免费课程来巩固这一块的认知:
https://www.imooc.com/learn/1101,如下几章课程