当前位置: 首页 > 科技观察

血淋淋的教训,请慎用InsertIntoSelect

时间:2023-03-19 20:55:15 科技观察

图片来自Pexels事件起因。公司交易量比较大,使用的数据库是MySQL。日增量在百万左右。,所以想要维持这张表的性能,只能考虑数据迁移。同事李某接到这个任务,于是想出了这两个方案:先通过程序查询,然后插入到历史表中,再删除原表。使用insertintoselect让数据库IO完成所有工作。在使用第一种方案的时候,发现一次加载所有负载,系统会直接OOM,但是分批进行会造成IO过多,耗时较长,所以选择了第二种方案。测试的时候没有问题,很开心。上网,然后被解雇。发生了什么?让我们回顾一下。先看第一种方案,先看伪代码://1.查询需要迁移的对应数据Listlist=selectData();//2.向历史表插入数据insertData(list);//3、删除原表数据deleteByIds(ids);从这段代码我们可以看出,OOM的原因很简单。我们直接把数据全部加载到内存中,内存不爆才奇怪。让我们看看第二种解决方案。发生了什么?为了在保留有效数据的同时保持表的性能,已经协商了一个量来保留10天的数据,表中必须保留将近1kw的数据。于是同事做了一个时间筛选的操作,直接insertintoselect...dateTime<(Tendaysago)...非常爽,而且直接避免了页面查询数据,也就没有OOM了。它还简化了很多代码操作并减少了网络问题。为了测试,专门搭建了1kw的数据进行仿真。当然,测试环境没有问题,顺利通过。考虑到这张表是支付流水表,所以把这个任务做成定时任务,安排在晚上8点执行。晚上的金额不是很大,自然没有问题,但是第二天公司财务组上班开始对账的时候,发现资金不匹配,大量周转没有入库。最终排查发现,晚上8:00以后,开始陆续出现无法插入支付记录的问题,并因此丢失大量数据。最后确定问题出在迁移任务上。一开始不知道,所以白天没问题,后来想到晚上出现这样的情况可能是受晚上任务的影响。最后停止了任务的二次online,发现没有问题了。这样的情况。重播有什么问题?为什么停止迁移任务?这个insertintoselect操作到底是做什么的?我们来看看这条语句的解释:从图中不难看出,这条查询语句直接做了全表扫描。这时候我们不难猜到一点问题。如果我们做全表扫描,我们的表这么大,是不是意味着迁移的时间会很长?如果我们的迁移时间是一个小时,是否说明了为什么我们白天没有出现这样的问题。但是全表扫描是最根本的原因吗?我们不妨在迁移和恢复站点的同时尝试做一些操作。最后还是会出现这样的问题。这时候我们可以调整一下,大胆假设一下,如果不全表扫描,是不是就不会出现这个问题呢?我们修改条件的时候发现没有全表扫描。最后再次还原场景,问题解决:断定是全表扫描导致了事故。这样做解决了发生的问题,但很难解释,因为它开始一个接一个地失败。原因是在默认的事务隔离级别下:insertintoaselectb操作a是直接锁表,b表一张一张锁。这也解释了为什么会接二连三的失败。逐项加锁时,由于水表大部分是复合记录,有的在扫描的时候最终被加锁,有的无法获取到锁,最终导致超时或者直接失败,有的成功了锁定过程。.为什么测试没有问题?测试的时候,正式环境的数据全部用于测试,但是不要忽视一个问题,那就是测试环境毕竟是测试环境。测试时,真实的数据量不代表就是真实的业务。场景。比如在这种情况下,就少了一种迁移时插入大量数据的情况。最终导致线上bug。解决方案既然可以通过避免全表扫描来解决,那么就可以避免了。为了避免全表扫描,对where后面的条件进行索引,让我们的select查询使用索引。插入还能工作吗?答案是:当然。综上所述,使用insertintoselect请慎重,一定要做好索引。作者:xlecho编辑:陶佳龙来源:https://juejin.cn/post/6931890118538199048