zoukankan      html  css  js  c++  java
  • 记一次业务引发的数据库死锁

    业务背景:

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

      云上除了需要实时查看各个网点上传的数据,还需要实时的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数据库死锁的产生原因及解决办法

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

      

  • 相关阅读:
    [JS11] 状态栏滚动
    [JS10] 获取时间
    [JS9] document's bgColor改变背景颜色
    [JS8] 显示从(0,0)到(0,0)的坐标
    [JS7] 显示从0到99的100个数字
    使用StringBuilder或StringBuffer简单优化
    启动一个线程的三种方法
    设置IE浏览器指定的功能
    jquery 判断元素是否存在于数组中
    Hibernate validator验证
  • 原文地址:https://www.cnblogs.com/fightingtong/p/12807009.html
Copyright © 2011-2022 走看看