记一次业务引发的数据库死锁

业务背景:

  把线下各网点的私有数据统一到云上管理,所有需要把线下数据不定时不定量的往云上同步

  云上除了需要实时查看各个网点上传的数据,还需要实时的j查询各个网点的统计数据

  数据库大体包一个基础表和一个归总表

业务实操:

  一开始的做法是,各个网点每次批量的往云上抛数据,云上预先把数据写入MQ,让上传l过程尽早返回。然后MQ再逐条进行分发消费。

  每消费一条数据,会重新count后更新到对应网点的中间表中

问题暴露:

  在开发库本地调试好功能后,没发现什么问题,于是丢到测试环境(这里的测试环境基本模拟实际生产环境)进行批量数据检测,

  发现同一网点上传了1000条数据上来,但是云平台只记录了几条数据,针对问题做了以下几点排查:

  1、根据上传记录日志缺点实际的上传条数(发现实实在在有1000条数据上来,基本可以推断问题出现在云上)

  2、查看云平台日志发现,系统有大量的死锁日志和锁超时的日志

  3、于是分析业务请求流程及数据库操作的逻辑,当请求并发(或者说高频访问)的存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况

  4、当前的sql写法为:update table1 set column=select count(1) from table2...

  5、就拿A、B两次请求来说,A访问table1,所以A持有table1的行锁,然后企图访问table2,但是B访问table2进行count,所以B持有table2的表锁,又企图访问table1的同行记录进行更新,但是由于table1的行记录被A持有,A由于B锁住了table2,所以他要等B释放了table2才能继续,同样B也要等A释放了table1的行锁才能继续,这就产生了死锁。

解决问题:

  找到问题的原因,我们需要通过控制业务的流程来规避死锁问题的产生

  于是我们在每一条上来数据后,不每次都做一次count更新,改成设置一个Redis缓存计数器,当消费数量等于上传的数量时,更新一次,通过降低更新频次避免死锁问题

  一切符合逻辑,于是乎在本地修改调试后又丢到测试环境去验证

引发第二个问题:

  发现日志的确没有再报死锁的问题了,但是发现汇总表并没有级联更新

  排查日志发现根本没有执行更新语句

  于是在执行更新的上下文条件记录日志,发现消费数量小于上传数量。难道因为保存所以导致部分数据未消费?

  查看并无相关的消费错误日志,且查看数据库数据,发现新增的数量与上传数量相等。这里更说明上传数据已经全部处理了。

  那么为什么消费数量的值会更小呢?查看测试环境发现MQ会分发给两台服务同时进行消费,虽然代码中的计数器关键部位有加锁,但是仅对单台服务程序有效,

  如果两台消费服务同时拿到缓存消费数量然后计数+1,这样就会少1,依次类推就会少更多的量

解决问题:

  定位到问题后,我们就需要一个能够控制全局的变量来控制计数器的值。于是我们引入了分布式锁,选用RedisSon做分布式锁,说干就干。

  再次本地调试后,提交到测试环境检测,发现网点汇总表的数量更新了,没有相关的日志错误,基本上到此问题已经处理完成。

推荐两篇死锁文章帮助理解:

  mysql数据库死锁的产生原因及解决办法

  手把手教你分析数据库死锁问题

  

 

记一次业务引发的数据库死锁

上一篇:SQLAlchemy(三):外键、连表关系


下一篇:MySQL 如何利用做排序