(1)问题继续排查:
简单来说,当经过排查,发现有大量的更新语句在活跃,而且有那种长期活跃的超长事务一直在跑没有结束,问了下负责人,发现后台跑了一个定时任务,定时清理数据,结果清理的时候
一下子清理了上千万的数据。这个清理怎么做的呢?居然开启了一个事务,然后一个事务里删除上千万数据,导致这个事务一直在运行,所以才看到上面发现的一些现象。
然后,这种长事务的运行会导致一个问题,那就是删除的时候仅仅是对数据加了一个删除标记,事实上并没有彻底删除,此时如果跟长事务同时运行的其他事务在查询,他在查询的时候是
可能会把那上千万被标记为删除的数据都扫描一遍的,因为每次扫描到一批数据,都发现被标记为删除了,接着继续往下扫描,所以才导致一些查询语句会那么慢。
那么,可能有人会问,为什么启动一个事务,在事务里查询,凭什么就要去扫描之前那个长事务标记为删除状态的上千万的垃圾数据?按说哪些数据都被删除了,可以不用扫描他们的。这个问题
的关键点在于,那个删除千万级数据的事务是个长事务,也就是说,当启动新事务查询的时候,那个删除千万级数据的长事务一直在运行,是活跃的!所以大家记得MVVC,提到的Read View概念嘛?
MVVC是如何实现的?不就是基于Read View机制来实现的?当启动一个新事务查询的时候,会生成一个Read View,里面包含了当前活跃事务的最大id,最小id和事务id集合,然后有一个判定规则,具体
判定规则。总之就是新事务查询的时候,会根据ReadView去判断那些数据是你可见的,以及可见的数据版本是哪个版本,因为一个数据有一个版本链条,有时候可能可见的仅仅是这个数据的一个历史版本
而已。
(undo log 多版本链和readview机制控制 事务 由于MySQL默认是RR的事务隔离级别,这样就是事务A查询时会去活跃列表里查看同一时刻的有哪些事务对事务A是不可见的 )
所以正是因为这个长事务一直在运行还在删除大量的数据,而且这些数据仅仅标记为删除,实际还没删除,所以此时新开事务的查询会读到所有被标记为删除的数据的,就会出现千万级别的数据
扫描,才会造成慢查询。
(2)解决:
所以解决方案也简单,直接kill掉那个正在删除千万级数据的长事务,所有SQL很快会恢复正常,从此以后,对于大量数据清理全部放到凌晨去执行,那么时候没什么人使用系统了,所以查询也很少。
类似问题:数据库慢sql查询看到很多commit语句超过2s。而且出现时间很随机。排查了几台,才发现出现这个问题的时候基本都在对一个历史记录表做数据清理工作,每次清理100万条
知识点:因为查询时,如果查到这些数据,都要在历史版本链中找自己能看到版本导致的查询慢;