某天,xxx接到请求,将A表的数据迁移到B表进行备份。他本来想通过程序查询找出来,然后批量插入,但是xxx觉得这样有点慢,而且需要大量的网络I/O,于是决定采用另一种方式来实现。在一定程度的海洋中游泳后,他发现insertintoselect可以避免使用网络I/O,直接使用SQL依赖数据库I/O来完成,不太好,然后他被开发除了。事故的发生是由于数据数据库中的order_today数据量大。当时好像是700W,每天以30W的速度递增。于是老板命令xxx将order_today中的部分数据迁移到order_record中,并删除order_today中的数据,从而减少order_today表的数据量。考虑到会占用数据库I/O,为了不影响业务,计划9:00后开始迁移,但是xxx在8:00尝试迁移了一小部分数据(1000条),觉得没什么问题,于是开始考虑批量迁移。在迁移过程中,应急组先响应了少量支付失败的用户,再响应了大量支付失败、初始化订单失败的用户。与此同时,腾讯也开始报警。然后xxx慌了,立刻停止了迁移。我以为停止迁移会恢复它,但它没有。你可以想象后来发生的事情。当时整个支付系统瘫痪了将近一个小时,客服电话都被打爆了。意外恢复在本地搭建精简版数据库,生成100w数据。模拟在线发生的事情。1、创建表结构order表如下:CREATETABLE`order_today`(`id`varchar(32)NOTNULLCOMMENT'主键',`merchant_id`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'商户号',`amount`decimal(15,2)NOTNULLCOMMENT'订单金额',`pay_success_time`datetimeNOTNULLCOMMENT'付款成功时间',`order_status`varchar(10)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'付款状态S:付款成功,F:订单付款失败',`remark`varchar(100)CHARACTERSETutf8COLLATEutf8_general_ciDEULENTNUL'备注'create_time`timestAmpNulldEfeaultCurrent_TimestAmpComment'创建创建',`Update_time_time`timestnotnulldfaultcurrent_timestemestemestamponupdatecurrent_timest_timest_timestampcomment'修改-InnoDBDEFAULTCHARSET=utf8;订单记录表如下:CREATETABLEorder_recordlikeorder_today;今天的订单表数据如下:2.模拟迁移将8号之前的数据迁移到order_record表:INSERTINTOorder_recordSELECT*FROMorder_todayWHEREpay_success_time<'2020-03-0800:00:00';在Navicat中运行迁移后的SQL,同时另开一个窗口插入数据,模拟顺序:从上面可以发现一开始可以正常插入,但是后面突然卡住了,过了23s才成功,才继续插入。此时已经迁移成功,可以正常插入了。这样做的原因是在默认的事务隔离级别下:insertintoorder_recordselect*fromorder_today加锁规则为:order_record表锁,order_today分步锁(扫描一锁一)。分析执行过程:通过观察迁移SQL的执行,会发现order_today是一次全表扫描,也就是说执行insertintoselectfrom语句时,MySQL会从上到下扫描order_today中的记录,锁定他们。来不来和直接锁表是一样的。这也可以解释为什么一开始只有一小部分用户支付失败,后面会出现大量用户支付失败和初始化订单失败,因为一开始只有一小部分数据被锁定,而数据未锁定的仍然可以正常修改。正常状态。由于被锁定的数据量越来越大,已经发生了大量支付失败的情况。最后全部锁死,导致无法插入订单,无法初始化订单。解决方案既然查询条件会导致order_today全表扫描,那么有什么办法可以避免全表扫描呢?很简单,只需要在pay_success_time字段中添加一个idx_pay_suc_time索引即可。因为索引查询,不会出现扫描全表并锁定表的情况,只会锁定满足条件的记录。最终的SQL:INSERTINTOorder_recordSELECT*FROMorder_todayFORCEINDEX(idx_pay_suc_time)WHEREpay_success_time<='2020-03-0800:00:00';执行过程如下:使用insertintotablAselect*fromtableB语句时,一定要保证where,orderaftertableB或者其他条件,需要有相应的索引,避免出现tableB所有记录都被锁住的情况.作者:不同科技之家编辑:陶佳龙来源:https://juejin.im/post/5e670f0151882549274a65ef
