什么是数据丢失

两个线程基于同一个查询结果进行修改,后修改的人会将先修改人的修改覆盖掉.

让我们先来看这么个小案例:

我们给游戏充值100,支付成功后,银行会向游戏服务器发送支付成功信息,有一个订单支付信息表(order)和一个账户信息表(account),首先要去order表中查询该订单支付状态state(select state from order where id=1)如果是0未支付,接下来要做的就是更新state为已支付,并且向你的账户account中加上100. 但是网络是有延迟的银行为了确保信息能送达可能会再次向服务器发送支付成功信息。这时第二次请求也是先去查询state,若此次请求和第一次正好是并发执行,他查询到的state也是0,所以也会给你的account加100块钱。也就是后来的修改覆盖掉了前边的修改,这就是数据丢失。

如何从重构数据仓库 重构数据库后游戏消失_隔离级别


操作流程如下:

如何从重构数据仓库 重构数据库后游戏消失_悲观锁_02

怎么防止数据丢失

先来看一下数据库中的锁机制:
共享锁:在非Serializable隔离级别做查询不加任何锁,而在Serializable隔离级别下做的查询加共享锁,
共享锁的特点:共享锁和共享锁可以共存,但是共享锁和排他锁不能共存
排他锁:在所有隔离级别下进行增删改的操作都会加排他锁,
排他锁的特点:和任意其他锁都不能共存

悲观锁:

悲观锁悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时就加上排他锁。
select state from order for update
这样第二个请求在查询时发现已经有个排他锁,就在那等着,等第一个事务操作完成后才轮到他,不过此时state已经变成1已支付,这样就避免再次加100块。

乐观锁:

乐观锁会乐观的认为每次查询都不会造成更新丢失.利用一个版本字段进行控制。
为order表加一个字段version
第一次请求查询出未支付后,在加100块钱之前,将更新state操作修改为:
update order set state=1 and version =version+1 where id=1 and version=0
这样第二个请求也执行这句是version就不是0了,执行失败。从而也防止了第一次数据的丢失。

两种方案对比

  • 查询非常多,修改非常少,使用乐观锁(悲观查询都加排他锁 效率会降低)
  • 修改非常多,查询非常少,使用悲观锁(多管理员修改时,某种一个可能一直等待,所以用悲观)