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

坑!只写了一行SQL,整个系统就挂了,

时间:2023-03-22 17:07:25 科技观察

前言请谨慎使用Insertintoselect。这天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的数据。模拟在线发生的事情。创建表结构ordertableCREATETABLE`order_today`(`id`varchar(32)NOTNULLCOMMENT'主键',`merchant_id`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'商户号',`amount`decimal(15,2)NOTNULLCOMMENT'订单金额',`pay_success_time`dateTimenotNullComment'支付支付',`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`update_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'修改时间--修改时自动更新',PRIMARYKEY(`id`)USINGBTREE,KEY`idx_merchant_id`(`merchant_id`)USINGBTREECOMDB'商户ID=)DEFACHATRECHANTE=表ENGINE=InTABLEorder_recordlikeorder_today;今天的order表数据迁移模拟将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全表扫描,有什么办法可以避免全表扫描呢?这很简单。只需将idx_pay_suc_time索引添加到pay_success_time字段。因为是索引查询,所以不会扫描全表。当表被锁定时,只有满足条件的记录才会被锁定。最后sqlINSERTINTOorder_recordSELECT*FROMorder_todayFORCEINDEX(idx_pay_suc_time)WHEREpay_success_time<='2020-03-0800:00:00';执行过程总结在使用insertintotablAselect*fromtableB语句时,一定要保证tableB后面的where、order或者其他条件,都需要有对应的索引,避免tableB的所有记录都被锁住的情况。