【震惊】小伙在公司用了个insert into select 竟然被开除了

血通常的教训,请慎用insert into select。同事应用以后,致使公司损失了近10w元,最终被公司开除。java

在这里插入图片描述

事情的原由

公司的交易量比较大,使用的数据库是mysql,天天的增量差很少在百万左右,公司并无分库分表,因此想维持这个表的性能只能考虑作数据迁移。mysql

同事李某接到了这个任务,因而他想出了这两个方案sql

  • 先经过程序查询出来,而后插入历史表,再删除原表
  • 使用insert into select让数据库IO来完成全部操做

第一个方案使用的时候发现一次性所有加载,系统直接就OOM了,可是分批次作就过多io和时间长,因而选用了第二种方案,测试的时候没有任何问题,开开心心上线,而后被开除。数据库

到底发生了啥,咱们复盘一下

  • 先来看第一个方案,先看伪代码
// 一、查询对应须要迁移的数据
List<Object> list = selectData();

// 二、将数据插入历史表
insertData(list);

// 三、删除原表数据
deleteByIds(ids);

咱们能够从这段代码中看到,OOM的缘由很简单,咱们直接将数据所有加载内存,内存不爆才怪。网络

在这里插入图片描述

  • 再来看看第二个方案,到底发生了啥

为了维持表的性能,同时保留有效数据,通过商量定了一个量,保留10天的数据,差很少要在表里面保留1kw的数据。因此同事就作了一个时间筛选的操做,直接insert into select ... dateTime < (Ten days ago),爽极了,直接就避免了要去分页查询数据,这样就不存在OOM啦。还简化了不少的代码操做,减小了网络问题。性能

为了测试,还特地建了1kw的数据来模拟,测试环境固然是没有问题啦,顺利经过。考虑到这个表是一个支付流水表,因而将这个任务作成定时任务,而且定在晚上8点执行。测试

晚上量也不是很大,天然是没有什么问题,可是次日公司财务上班,开始对帐,发现资金对不上,不少流水都没有入库。最终排查发现晚上8点以后,陆陆续续开始出现支付流水插入失败的问题,不少数据所以丢失。最终定位到了是迁移任务引发的问题,刚开始还不明因此,白天没有问题,而后想到晚上出现这样的状况多是晚上的任务出现了影响,最后停掉该任务的第二次上线,发现没有了这样的状况。code

复盘

  • 问题在哪里?

为何停掉迁移的任务以后就行了呢?这个insert into select操做到底作了什么?咱们来看看这个语句的explain。 在这里插入图片描述 咱们不难从图中看出,这个查询语句直接走了全表扫描。这个时候,咱们不难猜测到一点点问题。若是全表扫描,咱们这个表这么大,是否是意味着迁移的时间会很长?倘若咱们这个迁移时间为一个小时,那是否是意味着就解释了咱们白天没有出现这样问题的缘由了。可是全表扫描是最根本的缘由吗?blog

咱们不妨试试,一边迁移,一边作些的操做,还原现场。最终仍是会出现这样的问题。这个时候,咱们能够调整一下,大胆假设,若是不全表扫描,是否是就不会出现这样的问题。当咱们将条件修改以后,果真发现没有走了全表扫描了。最终再次还原现场,问题解决了 在这里插入图片描述索引

得出结论:全表扫描致使了此次事故的发生。

这样作就解决了发生的问题,可是作为陆陆续续开始失败这个就很差解释了。

  • 缘由

在默认的事务隔离级别下:insert into a select b的操做a表示直接锁表,b表是逐条加锁。这也就解释了为何出现陆续的失败的缘由。在逐条加锁的时候,流水表因为多数是复合记录,因此最终部分在扫描的时候被锁定,部分拿不到锁,最终致使超时或者直接失败,还有一些在这加锁的过成功成功了。

  • 为何测试没有问题?

在测试的时候充分的使用了正式环境的数据来测试,可是别忽视一个问题,那就是测试环境毕竟是测试环境,在测试的时候,数据量真实并不表明就是真实的业务场景。比方说,这个状况里面就少了一个迁移的时候,大量数据的插入这样的状况。最终致使线上bug

解决办法

既然咱们避免全表扫描就能够解决,咱们避免它就好了。想要避免全表扫描,对where后面的条件作索引,让咱们的select查询都走索引便可

insert into还能用吗?

能够

总结

使用insert into select的时候请慎重,必定要作好索引

相关文章
相关标签/搜索