前言

  Insert into select请慎用。这天xxx接到一个需求,需要将表A的数据迁移到表B中去做一个备份。本想通过程序先查询查出来然后批量插入。但xxx觉得这样有点慢,需要耗费大量的网络I/O,决定采取别的方法进行实现。通过在Baidu的海洋里遨游,他发现了可以使用insert into select实现,这样就可以避免使用网络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的数据。模拟线上发生的情况。

建立表结构

订单表

CREATE TABLE `order_today` (  `id` varchar(32) NOT NULL COMMENT '主键',  `merchant_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商户编号',  `amount` decimal(15,2) NOT NULL COMMENT '订单金额',  `pay_success_time` datetime NOT NULL COMMENT '支付成功时间',  `order_status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '支付状态  S:支付成功、F:订单支付失败',  `remark` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间 -- 修改时自动更新',  PRIMARY KEY (`id`) USING BTREE,  KEY `idx_merchant_id` (`merchant_id`) USING BTREE COMMENT '商户编号') ENGINE=InnoDB DEFAULT CHARSET=utf8;

订单记录表

CREATE TABLE order_record like order_today;

今日订单表数据

select count(*) from order_today;

模拟迁移

把8号之前的数据都迁移到order_record表中去。

INSERT INTO order_record SELECT
*
FROM
order_today
WHERE
pay_success_time < '2020-03-08 00:00:00';

在navicat中运行迁移的sql,同时开另个一个窗口插入数据,模拟下单。

从上面可以发现一开始能正常插入,但是后面突然就卡住了,并且耗费了23s才成功,然后才能继续插入。这个时候已经迁移成功了,所以能正常插入了。

出现的原因

  在默认的事务隔离级别下:insert into order_record select * from order_today 加锁规则是:order_record表锁,order_today逐步锁(扫描一个锁一个)。

分析执行过程。

  通过观察迁移sql的执行情况你会发现order_today是全表扫描,也就意味着在执行insert into select from 语句时,mysql会从上到下扫描order_today内的记录并且加锁,这样一来不就和直接锁表是一样了。

  这也就可以解释,为什么一开始只有少量用户出现支付失败,后续大量用户出现支付失败,初始化订单失败等情况,因为一开始只锁定了少部分数据,没有被锁定的数据还是可以正常被修改为正常状态。由于锁定的数据越来越多,就导致出现了大量支付失败。最后全部锁住,导致无法插入订单,而出现初始化订单失败。

解决方案

  由于查询条件会导致order_today全表扫描,什么能避免全表扫描呢,很简单嘛,给pay_success_time字段添加一个idx_pay_suc_time索引就可以了,由于走索引查询,就不会出现扫描全表的情况而锁表了,只会锁定符合条件的记录。

最终的sql

INSERT INTO order_record SELECT    * FROM    order_today FORCE INDEX (idx_pay_suc_time)WHERE    pay_success_time <= '2020-03-08 00:00:00';

执行过程

总结

  使用insert into tablA select * from tableB语句时,一定要确保tableB后面的whereorder或者其他条件,都需要有对应的索引,来避免出现tableB全部记录被锁定的情况。

参考文章

数据库的隔离级别为RR,会将你的select表记录锁住,如果没有索引,则会锁全表,不允许任何insert,delete,update操作,数据库隔离级别为RC,则不会有任何锁,所以建议你将数据库隔离级别调整为RC

禁止多表关联更新的,这种insert select最好是直接禁止操作,通过外部工具或代码来做归档最好。重量级操作、耗时操作、事务操作都得好好想想锁的问题、并发的问题

Insert into select语句引发的生产事故的更多相关文章

  1. 【Oracle】INSERT INTO SELECT语句和SELECT INTO FROM语句的区别

    >>>>>>>>>>>>>>>>>>>>>>>>> ...

  2. 【SQL】INSERT INTO SELECT语句与SELECT INTO FROM语句

    INSERT INTO SELECT语句与SELECT INTO FROM语句,都是将一个结果集插入到一个表中: #INSERT INTO SELECT语句 1.语法形式: Insert into T ...

  3. SQL中SELECT INTO和INSERT INTO SELECT语句介绍

    表复制是经常要用到的操作,下面就将为您介绍SQL中SELECT INTO和INSERT INTO SELECT语句,供您参考. Insert是T-sql中常用语句,Insert INTO table( ...

  4. 一次 select for update 的悲观锁使用引发的生产事故

    1.事故描述 本月 8 日上午十点多,我们的基础应用发生生产事故.具体表象为系统出现假死无响应.查看事发时间段的基础应用 error 日志,没发现明显异常.查看基础应用业务日志,银行结果处理的部分普遍 ...

  5. SQL insert into select 语句

    遇到权限数据变更的需要批量到别的平台, 在175平台添加一个权限需要, 批量到别的现有平台, 以后的建站, 会把sql放到自动建站里面; 权限的 insert into select 表一: `ous ...

  6. sql insert into select语句写法-将查询结果直接插入到表中

    insert into month_gpcj_info(idStr,zszrmygpsl,xyzrmygpsl,mycje,mycjl,month_date,dataCompiledDate) sel ...

  7. 数据库insert update select语句

    http://database.51cto.com/art/200903/113939_1.htm                   (更新语句) http://blog.csdn.net/chan ...

  8. 一次 Redis 事务使用不当引发的生产事故

    这是悟空的第 170 篇原创文章 官网:http://www.passjava.cn 你好,我是悟空. 本文主要内容如下: 一.前言 最近项目的生产环境遇到一个奇怪的问题: 现象:每天早上客服人员在后 ...

  9. SELECT INTO 和 INSERT INTO SELECT 两种表复制语句

    Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) values(value1,value2,...)这种形式的在应用程序开发中必不可少.但我 ...

随机推荐

  1. sqlldr导入报错:field in data file exceeds maximum length

    检查报错日志提示:field in data file exceeds maximum length REMARK字段设置:varchar2(2000),报错的内容也没有超1000个字符 表中定义的字 ...

  2. Typecho部署小破站

    写在前面 以前利用 Github Page + Hexo框架 + Next主题搭建过静态博客,没错就是那个黑白色系的网页!但是体验并不是很好,一来本身是静态网页,页面内容要修改都需要在本地修改完上传到 ...

  3. 五(一)、spring 声明式事务注解配置

    一.事务概述: 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用:比如 用户购买图书:购买动作之前需要确认 ①图书的数量是否足够:②用户账号余额是否足够 ...

  4. css书写规范 & 页面布局规范 &常用案例经验总结

    CSS 属性书写顺序(重点) 建议遵循以下顺序: 布局定位属性:display / position / float / clear / visibility / overflow(建议 displa ...

  5. v-html | 数据内容包含元素标签或者样式

    问题 如果我们展示的数据包含元素标签或者样式,我们想展示标签或样式所定义的属性作用,该怎么进行渲染 插值表达式{{}}和v-text指令被直接解析为了字符串元素. <body> <d ...

  6. python实现图像直方图

    目录: (一)直方图的使用 正文: (一)直方图的使用 1 from matplotlib import pyplot as plt 2 def plot_demo(image): 3 print(i ...

  7. [cf461D]Appleman and Complicated Task

    假设该矩形是aij,那么有a(i,j)=a(i-1,j-1)^a(i-1,j+1)^a(i-2,j),不断递归下去可以发现a(i,j)=a(1,y-x+1)^a(1,y-x+3)^--^a(1,x+y ...

  8. idea配置MyBatis

    新建工程 删掉src 创建Module 在工程中的porn.xml输入以下依赖 <?xml version="1.0" encoding="UTF-8"? ...

  9. mybatis-批量操作数据(list对象 )

    在实际工作中老是忘记 传入的参数和数据库参数名称要一致还是与实体类型一致导致很多笑话发生. 那我还是做个记录吧! dao层: int addRemark(@Param("list" ...

  10. html+css第四篇

    浮动 float浮动: 1.块在一排显示 2.内联支持宽高 3.默认内容撑开宽度 4.脱离文档流 5.提升层级半层 float:left | right | none | inherit; 文档流是文 ...