什么是行锁
在介绍行锁概念前,请想象一张用于统计年终礼品领取情况的在线Excel,其中每一行代表一条用户的礼品领取信息。假设多个人要同时编辑这张表格,但为了防止不同的人同时修改同一行导致混乱,每当有人编辑某一行时,在线Excel文档就会给这行加一个“锁”,这个锁相当于一个“正在编辑中”的标识,其他人只能等待当前编辑的人完成(释放锁),才能修改这一行的数据。
接下来引入行锁的概念:
行锁是数据表中行级数据的锁。
在代码开发中,我举一个下单业务场景的例子,用户A和用户B先后对一本书下单扣库存,那么在MySQL层面则需要执行类似update bound_info set quantity = quantity -1 where produce_id = 1;的逻辑,由于用户A和用户B下单的是同一个商品,MySQ InnoDB存储引擎的行锁需要先保证用户A扣完库存再执行用户B扣库存的逻辑,以保障并行情况下业务流转的正确性。
行锁的关键点
- 确保当多个事务同时操作同一行时,避免冲突或数据不一致。
- 只锁住正在被操作的那一行,其他行不受影响。
- 被当前事务锁住的行,其他事务需要等当前事务提交释放行锁时才可进行操作(两阶段锁协议-2PL)。
引用极客时间《MySQL实战45讲》中林晓斌老师的图片:
事务A先开始执行,事务B随后开始,两者同时更新id为1的数据行,结合InnoDB存储引擎的行锁机制,由于事务A先开始事务,此时对id为1的行进行加锁,那么即使事务B已经开始,那么依然要等到事务A提交释放行锁后才可开始执行。
行锁的实现
MySQL的行锁依赖于其不同的引擎实现的,若存储引擎不支持行锁,那么在并发场景下,只能使用范围更大的锁--表锁实现,当有事务在修改数据表任意一条数据时,操作数据表的其他事务都需要处于等待状态,极大降低了业务系统的并行量,企业级开发中MySQL的存储引擎一般选用InnoDB而不选择MyISAM,便是因为后者不支持行锁,而Innodb存储引擎提供了表锁。
行锁业务实战
什么是死锁
再次引用极客时间《MySQL实战45讲》中林晓斌老师的图片:
事务A、B同时开始,事务A更新id为1的行,获取id为1行的资源,对id = 1行的数据进行加锁,事务B随后更新id=2的数据行,获取id = 2行的资源,对id = 2行的数据进行加锁,接下来事务A需要获取id = 2行的资源,事务B需要获取id = 1行的资源,但是此时两事务均未提交释放资源,所以两个事务由于互相获取对方未释放的资源,导致进入互相的锁等待状态,即出现死锁。
在企业线上系统中难免会出现死锁的例子,在死锁问题中,一般有两种处理方式:
- 进入等待状态,直到超时。
- 发起死锁检测,主动回滚死锁中的某一个事务,释放锁,其他事务完成提交。
在MySQL中可以执行SHOW VARIABLES LIKE'innodb_lock_wait_timeout';超时时间,单位为秒。
以MySQL 8.0为例,超时时间默认设置为50秒,当死锁发生时,对于一个接口来说,响应时间超过50秒,用户是无法接受的,严重影响正常的生产环境业务,将超时等待时间设置的小是一种头疼医头疼的方式,指标不治本,而且会带来副作用,出现该问题的原因是因为MySQL出现了死锁,那么为什么不尽可能的减少锁等待时间、避免死锁出现的概率呢?
生产环境减少锁等待时间
依然是上文中下单业务的例子,用户A和用户B都需要下单扣库存,每个用户在支付时都需要经过以下步骤实现:
- 用户支付,扣除用户金额。
- 支付完成,扣除商家库存。
- 给用户发送下单成功信息,记录日志。
我们一起分析下下单扣库存的过程,用户支付需要扣除用户个人余额,更新时一般只有自己一个事务需要锁住用户信息、余额数据行,一般不会有其他事务需要获取该数据行的行锁;那么来看支付完完成时所做的事:由于用户A和用户B下单的是同一个商品,所以两个事务会锁同一行数据;下单成功发送信息,调用第三方SDK即可,插入数据到日志表是辅助流程,不会进行行锁。需要有一个前提知识:这三个过程SQL是在同一个事务中提交的,且同时成功或同时失败,那么让可能冲突的行尽可能占用更少的锁等待时间即可。如果把2这个流程放在第一位,那么该行的行锁需要从接口开始执行一直到事务提交才能释放,如果把可能存在行锁的流程放在最后执行,则行锁只需要等待扣除商家库存的SQL语句执行完成的时间,这样可以减少锁等待时间。
如何减少行锁对业务的影响
除了在代码上减少行锁等待的时间外,行锁的出现终究是因为系统并发量高,MySQL数据存储在磁盘上,出于设计上的限制,并发量大时性能下降明显,以至于影响业务运行速度和稳定性,为减少行锁对业务的影响:
- 如果服务器资源允许,可以使用中间件如MQ、Kafka等实现削峰的目的,在并发量不大时落库即可。
- 如果服务器资源不允许,可以使用Redis的锁和INCR功能限制系统中最大资源处理的数量,超过该数量时,可以直接返回,给于用户形如“当前活动火爆,请稍后再试”友好提示,以削弱客户端某时间段内的数量。
鱼和熊掌不可兼得,安全性和高并发不可能完美解决,只是寻找一个中间平衡点。
关于我
👋🏻你好,我是Debug.c。微信公众号:种棵代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼五年余的Java Coder。
🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。
📞如果您对我感兴趣,请联系我。
若有收获,就点个赞吧,喜欢原图请私信我。