MySQL实战45讲学习笔记:第三十一讲

1、本节概览

今天我要和你讨论的是一个沉重的话题:误删数据。mysql

在前面几篇文章中,咱们介绍了 MySQL 的高可用架构。固然,传统的高可用架构是不能预防误删数据的,由于主库的一个 drop table 命令,会经过 binlog 传给全部从库和级联
从库,进而致使整个集群的实例都会执行这个命令。sql

虽然咱们以前遇到的大多数的数据被删,都是运维同窗或者 DBA 背锅的。但实际上,只要有数据操做权限的同窗,都有可能踩到误删数据这条线。数据库

今天咱们就来聊聊误删数据先后,咱们能够作些什么,减小误删数据的风险,和由误删数据带来的损失。安全

为了找到解决误删数据的更高效的方法,咱们须要先对和 MySQL 相关的误删数据,作下分类:bash

1. 使用 delete 语句误删数据行;
2. 使用 drop table 或者 truncate table 语句误删数据表;
3. 使用 drop database 语句误删数据库;
4. 使用 rm 命令误删整个 MySQL 实例。架构

2、误删除行

在第 24 篇文章中,咱们提到若是是使用 delete 语句误删了数据行,能够用 Flashback工具经过闪回把数据恢复回来。运维

一、能够用 Flashback工具经过闪回把数据恢复回来。

Flashback 恢复数据的原理,是修改 binlog 的内容,拿回原库重放。而可以使用这个方案的前提是,须要确保 binlog_format=row 和 binlog_row_image=FULL工具

具体恢复数据时,对单个事务作以下处理:性能

1. 对于 insert 语句,对应的 binlog event 类型是 Write_rows event,把它改为Delete_rows event 便可;
2. 同理,对于 delete 语句,也是将 Delete_rows event 改成 Write_rows event;
3. 而若是是 Update_rows 的话,binlog 里面记录了数据行修改前和修改后的值,对调这两行的位置便可。

若是误操做不是一个,而是多个,会怎么样呢?好比下面三个事务:spa

(A)delete ...
(B)insert ...
(C)update ...

如今要把数据库恢复回这三个事务操做以前的状态,用 Flashback 工具解析 binlog 后,

写回主库的命令是:

(reverse C)update ...
(reverse B)delete ...
(reverse A)insert ...

也就是说,若是误删数据涉及到了多个事务的话,须要将事务的顺序调过来再执行。

二、须要说明的是,我不建议你直接在主库上执行这些操做。

须要说明的是,我不建议你直接在主库上执行这些操做。

恢复数据比较安全的作法,是恢复出一个备份,或者找一个从库做为临时库,在这个临时库上执行这些操做,而后再将确认过的临时库的数据,恢复回主库。

一、为何要这么作呢?

这是由于,一个在执行线上逻辑的主库,数据状态的变动每每是有关联的。可能因为发现数据问题的时间晚了一点儿,就致使已经在以前误操做的基础上,业务代码逻辑又继续修
改了其余数据。因此,若是这时候单独恢复这几行数据,而又未经确认的话,就可能会出现对数据的二次破坏

三、咱们不止要说误删数据的过后处理办法,更重要是要作到事前预防

固然,咱们不止要说误删数据的过后处理办法,更重要是要作到事前预防。我有如下两个建议:

1. 把 sql_safe_updates 参数设置为 on。这样一来,若是咱们忘记在 delete 或者update 语句中写 where 条件,或者 where 条件里面没有包含索引字段的话,这条语句的执行就会报错。

2. 代码上线前,必须通过 SQL 审计

一、设置了 sql_safe_updates=on,若是我真的要把一个小表的数据所有删掉,应该怎么办呢?

你可能会说,设置了 sql_safe_updates=on,若是我真的要把一个小表的数据所有删掉,应该怎么办呢?

若是你肯定这个删除操做没问题的话,能够在 delete 语句中加上 where 条件,好比where id>=0。

可是,delete 全表是很慢的,须要生成回滚日志、写 redo、写 binlog。因此,从性能角度考虑,你应该优先考虑使用 truncate table 或者 drop table 命令。

二、delete、truncate table、drop table的区别

使用 delete 命令删除的数据,你还能够用 Flashback 来恢复。而使用 truncate /droptable 和 drop database 命令删除的数据,就没办法经过 Flashback 来恢复了。为何呢?

这是由于,即便咱们配置了 binlog_format=row,执行这三个命令时,记录的 binlog 仍是 statement 格式。binlog 里面就只有一个 truncate/drop 语句,这些信息是恢复不出数据的。

那么,若是咱们真的是使用这几条命令误删数据了,又该怎么办呢?

3、误删表/库

这种状况下,要想恢复数据,就须要使用全量备份,加增量日志的方式了。这个方案要求线上有按期的全量备份,而且实时备份 binlog。

在这两个条件都具有的状况下,假若有人中午 12 点误删了一个库,恢复数据的流程以下:

1. 取最近一次全量备份,假设这个库是一天一备,上次备份是当天 0 点;
2. 用备份恢复出一个临时库;
3. 从日志备份里面,取出凌晨 0 点以后的日志;
4. 把这些日志,除了误删除数据的语句外,所有应用到临时库。

这个流程的示意图以下所示:

图 1 数据恢复流程 -mysqlbinlog 方法


关于这个过程,我须要和你说明以下几点:

1. 为了加速数据恢复,若是这个临时库上有多个数据库,你能够在使用 mysqlbinlog 命令时,加上一个–database 参数,用来指定误删表所在的库。这样,就避免了在恢复数
据时还要应用其余库日志的状况。

2. 在应用日志的时候,须要跳过 12 点误操做的那个语句的 binlog:

若是原实例没有使用 GTID 模式,只能在应用到包含 12 点的 binlog 文件的时候,先用–stop-position 参数执行到误操做以前的日志,而后再用–start-position 从误
操做以后的日志继续执行;

若是实例使用了 GTID 模式,就方便多了。假设误操做命令的 GTID 是 gtid1,那么只须要执行 set gtid_next=gtid1;begin;commit; 先把这个 GTID 加到临时实例的
GTID 集合,以后按顺序执行 binlog 的时候,就会自动跳过误操做的语句。

不过,即便这样,使用 mysqlbinlog 方法恢复数据仍是不够快,主要缘由有两个:

1. 若是是误删表,最好就是只恢复出这张表,也就是只重放这张表的操做,可是mysqlbinlog 工具并不能指定只解析一个表的日志;

2. 用 mysqlbinlog 解析出日志应用,应用日志的过程就只能是单线程。咱们在第 26 篇文章中介绍的那些并行复制的方法,在这里都用不上。

一种加速的方法是,在用备份恢复出临时实例以后,将这个临时实例设置成线上备库的从库,这样:

1. 在 start slave 以前,先经过执行change replication filter replicate_do_table = (tbl_name) 命令,就可让临时库只同步误操做的表;
2. 这样作也能够用上并行复制技术,来加速整个数据恢复过程。

这个过程的示意图以下所示。

 图 2 数据恢复流程 -master-slave 方法

能够看到,图中 binlog 备份系统到线上备库有一条虚线,是指若是因为时间过久,备库上已经删除了临时实例须要的 binlog 的话,咱们能够从 binlog 备份系统中找到须要的
binlog,再放回备库中。

假设,咱们发现当前临时实例须要的 binlog 是从 master.000005 开始的,可是在备库上执行 show binlogs 显示的最小的 binlog 文件是 master.000007,意味着少了两个
binlog 文件。这时,咱们就须要去 binlog 备份系统中找到这两个文件。

把以前删掉的 binlog 放回备库的操做步骤,是这样的:

1. 从备份系统下载 master.000005 和 master.000006 这两个文件,放到备库的日志目录下;
2. 打开日志目录下的 master.index 文件,在文件开头加入两行,内容分别是“./master.000005”和“./master.000006”;
3. 重启备库,目的是要让备库从新识别这两个日志文件;
4. 如今这个备库上就有了临时库须要的全部 binlog 了,创建主备关系,就能够正常同步了。

不管是把 mysqlbinlog 工具解析出的 binlog 文件应用到临时库,仍是把临时库接到备库上,这两个方案的共同点是:误删库或者表后,恢复数据的思路主要就是经过备份,再加
上应用 binlog 的方式。

也就是说,这两个方案都要求备份系统按期备份全量日志,并且须要确保 binlog 在被从本地删除以前已经作了备份。

可是,一个系统不可能备份无限的日志,你还须要根据成本和磁盘空间资源,设定一个日志保留的天数。若是你的 DBA 团队告诉你,能够保证把某个实例恢复到半个月内的任意
时间点,这就表示备份系统保留的日志时间就至少是半个月。

另外,我建议你不论使用上述哪一种方式,都要把这个数据恢复功能作成自动化工具,而且常常拿出来演练。为何这么说呢?

这里的缘由,主要包括两个方面:

1. 虽然“发生这种事,你们都不想的”,可是万一出现了误删事件,可以快速恢复数据,将损失降到最小,也应该不用跑路了。

2. 而若是临时再手忙脚乱地手动操做,最后又误操做了,对业务形成了二次伤害,那就说不过去了。

4、延迟复制备库

虽然咱们能够经过利用并行复制来加速恢复数据的过程,可是这个方案仍然存在“恢复时间不可控”的问题。

若是一个库的备份特别大,或者误操做的时间距离上一个全量备份的时间较长,好比一周一备的实例,在备份以后的第 6 天发生误操做,那就须要恢复 6 天的日志,这个恢复时间
多是要按天来计算的。

那么,咱们有什么方法能够缩短恢复数据须要的时间呢?

若是有很是核心的业务,不容许太长的恢复时间,咱们能够考虑搭建延迟复制的备库。这个功能是 MySQL 5.6 版本引入的。

通常的主备复制结构存在的问题是,若是主库上有个表被误删了,这个命令很快也会被发给全部从库,进而致使全部从库的数据表也都一块儿被误删了。

延迟复制的备库是一种特殊的备库,经过 CHANGE MASTER TO MASTER_DELAY = N命令,能够指定这个备库持续保持跟主库有 N 秒的延迟。

好比你把 N 设置为 3600,这就表明了若是主库上有数据被误删了,而且在 1 小时内发现了这个误操做命令,这个命令就尚未在这个延迟复制的备库执行。这时候到这个备库上

执行 stop slave,再经过以前介绍的方法,跳过误操做命令,就能够恢复出须要的数据。这样的话,你就随时能够获得一个,只须要最多再追 1 小时,就能够恢复出数据的临时实
例,也就缩短了整个数据恢复须要的时间。

5、预防删库/表的方法

虽然常在河边走,很难不湿鞋,但终究仍是能够找到一些方法来避免的。因此这里,我也会给你一些减小误删操做风险的建议。

第一条建议是,帐号分离。这样作的目的是,避免写错命令。好比:

咱们只给业务开发同窗 DML 权限,而不给 truncate/drop 权限。而若是业务开发人员有 DDL 需求的话,也能够经过开发管理系统获得支持。

即便是 DBA 团队成员,平常也都规定只使用只读帐号,必要的时候才使用有更新权限的帐号。

第二条建议是,制定操做规范。这样作的目的,是避免写错要删除的表名。好比:

在删除数据表以前,必须先对表作更名操做。而后,观察一段时间,确保对业务无影响之后再删除这张表。

改表名的时候,要求给表名加固定的后缀(好比加 _to_be_deleted),而后删除表的动做必须经过管理系统执行。而且,管理系删除表的时候,只能删除固定后缀的表。

6、rm删除数据

其实,对于一个有高可用机制的 MySQL 集群来讲,最不怕的就是 rm 删除数据了。只要不是恶意地把整个集群删除,而只是删掉了其中某一个节点的数据的话,HA 系统就会开
始工做,选出一个新的主库,从而保证整个集群的正常工做。

这时,你要作的就是在这个节点上把数据恢复回来,再接入整个集群。

固然了,如今不止是 DBA 有自动化系统,SA(系统管理员)也有自动化系统,因此也许一个批量下线机器的操做,会让你整个 MySQL 集群的全部节点都全军覆没。

应对这种状况,个人建议只能是说尽可能把你的备份跨机房,或者最好是跨城市保存。

7、小结

今天,我和你讨论了误删数据的几种可能,以及误删后的处理方法。但,我要强调的是,预防远比处理的意义来得大。

另外,在 MySQL 的集群方案中,会时不时地用到备份来恢复实例,所以按期检查备份的有效性也颇有必要。

若是你是业务开发同窗,你能够用 show grants 命令查看帐户的权限,若是权限过大,能够建议 DBA 同窗给你分配权限低一些的帐号;你也能够评估业务的重要性,和 DBA 商量
备份的周期、是否有必要建立延迟复制的备库等等。

数据和服务的可靠性不止是运维团队的工做,最终是各个环节一块儿保障的结果。

今天的课后话题是,回忆下你亲身经历过的误删数据事件吧,你用了什么方法来恢复数据呢?你在这个过程当中获得的经验又是什么呢?

你能够把你的经历和经验写在留言区,我会在下一篇文章的末尾选取有趣的评论和你一块儿讨论。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一块儿阅读。

8、上节提问时间

我在上一篇文章给你留的问题,是关于空表的间隙的定义。

一个空表就只有一个间隙。好比,在空表上执行:

begin;
select * from t where id>1 for update;

这个查询语句加锁的范围就是 next-key lock (-∞, supremum]。验证方法的话,你可使用下面的操做序列。你能够在图 4 中看到显示的结果。

图 3 复现空表的 next-key lock

图 4 show engine innodb status 部分结果

评论区留言点赞板:

@老杨同志 给出了正确的分析和 SQL 语句验证方法;@库淘淘 指出了 show engine innodb status 验证结论。

相关文章
相关标签/搜索