更新丢失是指并发下两次更新同时进行,后一次更新覆盖了前一次更新的情况,更新丢失是数据没有保证一致性导致的.举个栗子:
用户 A 在银行卡有 100 元钱,某一刻用户 B 向 A 转账 50 元(称为 B 操作),同时有用户 C 向 A 转账 50 元(称为 C 操作);
B 操作从数据库中读取他此时的余额 100,计算新的余额为 100+50=150
C 操作也从数据库中读取他此时的余额 100,计算新的余额为 100+50=150
B 操作将 balance=150 写入数据库, 之后 C 操作也将 balance=150 写入数据库
最终 A 的余额变为 150
上面的例子,A 同时收到两笔 50 元转账,最后的余额应该是 200 元,但却因为并发的问题变为了 150 元,原因是 B 和 C 向 A 发起转账请求时,同时打开了两个数据库会话,进行了两个事务,后一个事务拿到了前一个事务的中间状态数据,导致更新丢失.常用的解决思路有两种:
加锁同步执行
update 前检查数据一致性
悲观锁
顾名思义,悲观锁在读取数据的时候都会认为会有别人去修改,于是在取数据的时候会对当前数据加一个锁, 在操作结束前,不允许其余操作更改.要注意悲观锁和乐观锁都是业务逻辑层次的定义,不同的设计可能会有不同的实现.在 mysql 层常用的悲观锁实现方式是加一个排他锁.
排他锁
查阅资料很多对排他锁的解释是:" 排他锁通过在事务中使用
select xx for update
语句来实现,排他锁会在当前行加一个行级锁,在当前事务提交前,其余事务无法进行 update 操作."
然而实际上并不是这样,实际上是加了排他锁的数据,在释放锁(事务结束)之前其他事务不能再对该数据加锁
排他锁之所以能阻止 update,delete 等操作是因为 update,delete 操作会自动加排他锁
也就是说即使加了排他锁也无法阻止 select 操作.而 select XX for update 语法可以对 select 操作加上排他锁.所以为了防止更新丢失可以在 select 时加上 for update 加锁 这样就可以阻止其余事务的 select for update(但注意无法阻止 select)example:
begin;
select * from account where id = 1 for update;
update account set balance=150 where id =1;
commit;
这样在 B 操作提交前, C 操作无法获得排他锁,从而避免对 account 的重复更新导致的更新丢失.
乐观锁
乐观锁是指在获取数据时候不加锁,乐观的认为操作不会有冲突,在 update 的时候再去检查冲突.example:
begin;
select balance from account where id=1;
-- 得到balance=100;然后计算balance=100+50=150
update account set balance = 150 where id=1 and balance = 100;
commit;
如上,如果 sql 在执行的过程中发现 update 的 affected 为 0 说明 balance 不等于 100 即该条数据有被其余事务更改过,此时业务上就可以返回失败或者重新 select 再计算
来源: https://juejin.im/entry/5a6828956fb9a01c9332d83e