数据库死锁原因回溯

业务背景:

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

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

业务实操:

一开始的做法是,各个网点每次批量的往云上上传数据,云端上先把数据写入MQ,让上传过程尽早返回。然后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做分布式锁,说干就干。

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

php工作日志thinkphp

PHP7.2以上版本PHPEXCEL无法导出问题

2021-1-11 14:54:16

工作日志随笔

window 安装搭建 RabbitMQ

2021-3-4 9:20:42

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧