隐藏多年的Bug,今天终于连根拔起,悲观锁其实没你想象的那么容易(实战案例)

本文讲述了作者在项目中遇到的并发账务不平问题,通过深入探讨悲观锁的原理和使用,揭示了一个隐藏的Bug。在解决过程中,涉及数据库事务、多线程、日志分析、SQL优化等多个方面。最终发现是由于Hibernate的缓存机制导致悲观锁失效,并给出了解决方案。
摘要由CSDN通过智能技术生成

接手的新项目,接二连三的出现账不平的问题,作为程序员中比较执着的人,不解决誓不罢休。最终,经过两次,历时多日终于将其连根拔起。实属不易,特写篇文章记录一下。

文章中不仅会讲到使用悲观锁踩到的坑,以及本人是如何排查问题的,某些思路和方法或许能对大家有所帮助。

事情的起源

运营同事时不时就提出查账调账的需求,原因很简单,账不平,不查不行。如果你有过财务相关系统的工作经历,账务问题始终是最难攻克的。

虽然刚接手项目,虽然很多业务逻辑还不了解,但出现这样的技术挑战,还是要坚决攻克的。

其实,这类问题的原因很简单:热点账户。当很多服务或线程操作同一个用户的账户时,就会出现一个更新把另外一个更新覆盖掉的情况。

上图可轻易看出,当两个服务或线程同时查询数据库的一条数据(热点账户),然后内存中做修改,最后更新到数据库。如果出现并发情况,两个线程都读取了100,一个计算得80,一个计算得60,后更新的就有可能将前面的覆盖掉。

解决方案通常有:

  • 单服务线程锁;

  • 集群分布式锁;

  • 集群数据库悲观锁;

项目中已采用了悲观锁,就基于来进行排查追踪原因。

何谓悲观锁

悲观锁是在对数据被的修改持悲观态度,在整个数据处理过程中会将数据锁定。

悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在应用层中实现了加锁机制,也无法保证外部系统不会修改数据)。

通常会使用select ... for update语句来实现对数据的枷锁。

for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。

如下示例展示了悲观锁的基本使用流程:

set autocommit=0;  
//设置完autocommit后,执行正常业务。具体如下:
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_o
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值