。

数据是怎么删除的 & 数据恢复
01
定位数据是如何被删除的
回到公司的第一件事情,就是向当事人确认数据是何时、如何删除的。
事件还原经过:
被删表所在的数据库是即将要上线新功能所在的库,因与该系统实例上其他库无业务关联且独立,初始化的数据早在一月前就部署到了生产环境,但此库还未正式启用。
因上线的新功能涉及到和我司其余系统的交互,被删表所在库产生了上线前1天与其他系统联调功能验证的上线数据和中间测试数据。上线之际,在做最后程序调试和数据校验过程中,删除测试数据的时候意外发生了。
当事人本想使用以下SQL完成测试数据清理工作:
DELETE FROM [table_name] t1 WHERE id NOT IN (SELECT t2_id FROM [table_name] t2);
DELETE FROM [table_name] WHERE id;

02
数据恢复
/usr/local/mysql3306/bin/mysqldump -uroot -p --single-transaction --set-gtid-purged=off -B [database_name] > /backup/[database_name]_`date +%Y-%m-%d_%H-%M-%S`.sql
DROP DATABASE [database_name];
备份数据还原:
mysql3306 -p < /backup/[database_name]_`date +%Y-%m-%d_%H-%M-%S`.sql
不到半个小时,在大家伙的共同努力下,混乱数据的清理和数据恢复工作全部完成。重新联调写入的过程也没有发生什么问题。但上线代码过程中还是出了一点小插曲,不过最终在凌晨03:00左右全部上线成功并验收完毕。



误删数据后的思考和测试
DELETE FROM [table_name] WHERE id;
据当事人回忆,主观认为这样书写SQL的形式理应该报错,没想到执行成功了。被删表的表结构如下:
CREATE TABLE `xxxxxx` ( `id` int(11) NOT NULL AUTO_INCREMENT, `app_key` varchar(64) COLLATE utf8_unicode_ci NOT NULL, `app_name` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL, `owner_user_id` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'xxx', `category_id` int(64) DEFAULT NULL COMMENT 'xxx', `create_date` datetime DEFAULT NULL COMMENT 'xxx', `modify_date` datetime DEFAULT NULL COMMENT 'xxx', `icon_url` longtext COLLATE utf8_unicode_ci COMMENT 'xxx', `status` varchar(4) COLLATE utf8_unicode_ci DEFAULT '0' COMMENT 'xxx', `merge_to_app` int(11) DEFAULT NULL COMMENT 'xxx', `merge_num` int(11) NOT NULL DEFAULT '0' COMMENT 'xxx', PRIMARY KEY (`id`), KEY `owner_user_id` (`owner_user_id`), KEY `create_date` (`create_date`), KEY `app_key` (`app_key`), KEY `application_app_name_IDX` (`app_name`) USING BTREE, KEY `application_status_IDX` (`status`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='xxxxxxxx';
老样子,还是准备一张测试表,插入一些测试数据。
CREATE TABLE `student_info` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '学生编号', `student_name` varchar(20) NOT NULL COMMENT '学生姓名', `address` varchar(100) DEFAULT '北京市' COMMENT '家庭住址', `extra` varchar(50) DEFAULT NULL COMMENT '额外信息', `remark` tinytext COMMENT '备注', PRIMARY KEY (`id`), UNIQUE KEY `uniq_extra` (`extra`)) ENGINE=InnoDB COMMENT='学生表信息表';INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10001, '赵甲', '北京市前门大街', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10002, '钱乙', '北京市东直门', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10003, '孙丙', '北京市西直门', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10004, '李丁', '北京市望京', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10005, '周戊', '北京市海淀区', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10006, '吴己', '北京市朝阳区', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10007, '郑庚', '北京市西城区', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10008, '冯戌', '北京市石景山区', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10009, '褚壬', '北京市昌平区', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(10010, '卫癸', '北京市小汤山', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(20001, 'Danny', 'America', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(20002, 'Jenny', 'Canada', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(20003, '工藤新一', 'Japan', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(20004, '王子', '北京市', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(20005, '武丑', '北京市', NULL, NULL);INSERT INTO student_info (id, student_name, address, extra, remark) VALUES(30001, '蒋寅', '上海市浦东新区', '交换生', NULL);SELECT * FROM student_info;

测试场景1:DELETE列值为NULL的tinytext字符串类型字段。
DELETE FROM student_info WHERE remark;

测试场景2:DELETE列值部分为NULL的varchar字符串类型字段。
DELETE FROM student_info WHERE extra;

测试场景3:DELETE列值都有数据的varchar字符串类型字段。
DELETE FROM student_info WHERE student_name;

结果:执行报错,ERROR 1292 (22007): Truncated incorrect DOUBLE value: '赵甲',数据不会被删除。
测试场景4:模拟生产环境误删数据操作。DELETE列值都有数据的int整型类型字段。
DELETE FROM student_info WHERE id;

结果:数据全部被删除。
测试场景5:DELETE列值部分有数据的double双精度数字类型字段。
上面我们的测试数据被删除了,我们再来构造一些测试数据。
ALTER TABLE student_info ADD COLUMN age DOUBLE DEFAULT NULL AFTER address;INSERT INTO student_info (id, student_name, address, age, extra, remark) VALUES(10001, '赵甲', '北京市前门大街', NULL, NULL, NULL);INSERT INTO student_info (id, student_name, address, age, extra, remark) VALUES(10002, '钱乙', '北京市东直门', 20.5, NULL, NULL);INSERT INTO student_info (id, student_name, address, age, extra, remark) VALUES(10003, '孙丙', '北京市西直门', 22.3, NULL, NULL);INSERT INTO student_info (id, student_name, address, age, extra, remark) VALUES(10004, '李丁', '北京市望京', 21.6, NULL, NULL);SELECT * FROM student_info;

执行测试语句:
DELETE FROM student_info WHERE age;

结果:匹配到数据的行都被删除了。


小结
DESC FORMAT=JSON DELETE FROM student_info WHERE address\G

SELECT id FROM student_info WHERE `employee`.`student_info`.`id` <> 0;SELECT 0 <> '北京市前门大街';SELECT 0 <> NULL;

经过这次误删数据的栗子🌰,得到了以下警示:
1、在执行UPDATE、DELETE操作时一定、一定、一定要注意书写格式,不要再使用"DELETE FROM [table_name] WHERE [字段];"这种SQL写法执行DELETE、UPDATE操作,侥幸的话,字段类型是字符串,无损;不幸的话,全表被删。所以还是不要抱有侥幸心理。
2、对生产环境心生敬畏,不要在执行框、命令行里直接拼写SQL,测试通过后再上线生产环境;最好先将DELETE、UPDATE语句的匹配条件改写成SELECT语句查看即将操作的数据是否属实。有条件的可以每次执行操作前都看一下执行计划,毕竟执行计划不会执行,以便提前发现问题。
3、备份的重要性。
4、团队合作的力量,众人拾柴火焰高。
工作中我们不可能遇到所有的问题,今天这个案例就是我自己经验累积的过程。大家是否也遇到过类似的情况呢?希望今天的文章能给大家带来收获,每天进步一点点,与君共勉。

end




