数据库锁实现秒杀活动

        在没有使用缓存技术前,怎么处理高并发?在没有众多消息中间件或高级工具前,怎么处理?之前本人将工作中参加的秒杀活动实践过程写成一篇文章供大家参考。文章描述的实现过程是通过redis分布式锁实现的,利用了redisson的原子类RAtomicLong,在缓存中操作库存,从而满足数据一致性和高并发的场景要求。实践下来缓存的方便之处不言而喻。文章地址:一次秒杀活动https://blog.csdn.net/Wrcode/article/details/99483758

        可是分布式锁的使用在并发不高的时候显得大马拉小车,完全可以使用一些更为简单的方式,同时又在并发很大(如:双十一、十二、各种电商大促活动)场景下,显得心有余而力不足。因此,在同事的建议下,本人会再写两篇文章聊聊并发低流量、高流量(描述不够准确,明白意思即可)时候的处理思路。我们就使用基础的技术工具-数据库锁,去解决这个热门的问题。  谈起锁,大家常想到的是:乐观锁、悲观锁、锁粒度:行锁、表锁、页面锁等。其实锁远不止如此,当中有很多的细节都被忽略掉了。关于具体的锁基础大家可以参考:https://dev.mysql.com/doc/refman/5.6/en/,本文不再赘述。

背景:有库存数要求的秒杀活动(具体细节不再赘述,见上述文章提到的)

工具:mysql 5.6(Innodb引擎)

1、乐观锁:

        数据库本身没有提供乐观锁的技术实现,更多的是谈及一种思想。乐观锁认为不会出现同时修改数据情况,故而使用某种标识检验数据一致性即可。大家熟知的是使用版本号version,其实就是将版本号作为修改数据的参考依据。首先拿着version和库存stock表的stock_version比较,请求的version = stock_version相同则认为数据一致,可以将库存(stock_num)减一,同时将stock_version做自增操作。当version < stock_version(stock_versiony永远大于等于用户手中的version),说明数据已经发生改变,需要重新发起请求,直至请求成功或库存为0。以下为一个简单执行流程:

(1)有多个用户请求,使用如下sql查询库存,在库存未减为0之前,每个请求得到结果均为有库存,且会带一个版本号

// 查询结果包含stock_num、stock_version
select * from stock where stock_num>1;

(2)当用户A查询有库存,在做减库存操作前,用户B查询也有库存。那么A发起减库存请求执行如下sql

# 用户A拿着上一个sql中的stock_version,发起减库存请求
update stock set stock_num = stock_num-1, stock_version = stock_version+1 where stock_version=#{versoin} and stock_num>0;

(3)此时用户B也发起减库存请求。但是stock_version已经大于B手中version,条件显然不满足。只能跳转至(1)重新发起。

// 显然此时stock_version > version,版本标识已经一致,无法减库存
...... where stock_version=#{version} and stock_num>0;

以上,即为使用乐观锁的库存操作思路,其实只要能提供给一个记录标识,每次去检验用户手中的标识与库存中的标识一致性。

2、悲观锁:

       悲观锁对于数据的安全一致性有着更高的要求,它认为数据在修改期间会有其他的事务来修改,因此,需要一种更加绝对的独占的方式去更新数据。innodb引擎下的exclusive(X)锁(排他锁)就是一种悲观锁。关于innodb引擎大家可参考:https://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html。以下为悲观锁的减库存实现方式:

(1)仍然模拟A、B用户请求。首先,开启事务,在查询库存的sql语句后带上 for update。当用户A首先进入事务,执行for update,A便持有了数据库锁。B用户只能等待A释放锁后继续,否则会一致处于等待状态。

......

// 开启事务
beginTransaction();

// 查询有结果则代表有库存
select * from stock where stock_id = #{id} and stock_num>0 for update;

// 判断是否有查询结果,若无代表库存已经为空,提交事务,退出。

(2)查询有结果则代表有库存。用户A在事务内做减库存操作后提交事务,A用户到此抢到商品。

// A用户更新库存
update stock set stock_num = stock_num-1 where stock_id=#{id} and stock_num>0;

// 记录用户A和所属库存关系

// 提交事务
commitTrancation();

......

(3)A提交事务,释放锁后,B得到锁,开始执行步骤(1),此时有其他请求也会先等待直至B释放锁。如果有库存则B再更新

若已经没有,则提交退出。

        以上为悲观锁实现的秒杀减库存流程。相比乐观锁,悲观锁对于数据的管控会更加严格,但带来的就是更高的资源消耗。大家根据具体的业务情况来挑选合适的。在实现中仍有许多的技术细节,需要考量。因为innodb提供了行锁级别的锁粒度以及事务。所以,上述流程均在该引擎下实现。mysql还有其他许多流程,可能也会更高效的实现方案,请大家评论中提点。希望,本片文章给大家启发。

关于mysql的锁、优化、编码、索引、存储结果均在官方文档中给出,大家可参考:

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值