31 数据误删的恢复
1 使用delete语句误删数据行
2 使用drop table或者truncate table语句误删除数据表
3 使用drop database语句误删数据库
4 使用rm命令误删整个mysql实例
误删行
如果使用delete语句误删了数据行,可以使用flushback工具通过闪回把数据恢复出来
Flushback恢复数据的原理,是修改binlog的内容,拿回原库重放,使用flushback工具恢复的前提是,需要确保binlog_format=row和binlog_row_image=FULL.
具体恢复数据时,对单个事务做如下处理:
1 对于insert语句,对于的binlog event类型是Write_rows event,把它改成Delete_row event即可
2 同理,对于delete,也是将Delete_rows event改为Write_rows event;
3 而如果是Update_rows的话,binlog里面记录了数据行修改前和修改后的值,对调这行的位置即可
如果误操作不是一个,而是多个,比如下面3个事务
(A) delete ...
(B) insert ...
(C)update ...
现在要把数据库恢复这3个事务操作之前的状态,用flashback工具解析binlog后,写回主库的命令是:
(reverse C)update ...
(reverse B)delete ...
(reverse A)insert ...
也就是说,如果误删除数据涉及到了多个事务的话,需要将事务的顺序调过来再执行。
需要说明的是,不建议这些操作直接在主库上进行回放
恢复数据比较安全的做法是,恢复出一个备份,或者找一个从库作为临时库,在这个临时库上执行这些操作,然后再将确认过的临时库的数据,恢复回主库。
防止在数据误删的情况下,业务逻辑又继续更改其他的数据,所以这时候单恢复这几行数据,而又未经确认的话,可能会出现对数据的二次破坏。
防止数据误删的事前预防,有两个建议:
1 把sql_safe_updates参数设置为on,这样一来,如果忘记在delete或者update的语句写where条件,或者where条件里面没有包含索引字段的话,这条语句的执行就会报错。
2 代码上线前,必须经过sql审计。
如果确定这个删除操作没问题的话,可以在delete语句中加上where条件,比如where id>0;
但是delete全表很慢的,而且需要写回滚日志、写redo、写binlog,所以从性能角度考虑,应该优先考虑使用truncate或者drop table命令。
使用delete命令删除的数据,可以用flashback来恢复,而使用truncate/drop table和drop database命令删除的数据,就没有办法使用flashback来恢复。因为设置了binlog_format=row,执行这3个命令时,binlog里面只有一个truncate/drop语句,这些信息是恢复不出数据的。
误删库/表
这种情况下,要想恢复数据,就需要使用全量备份,加增量日志的方式,这个方案要求线上有定期的全量备份,并且实时备份binlog。
在这2个条件下,假如有人中午12点误删了一个库,恢复数据的流程如下:
1 取最近一次全备份,假设这个库一天一个备份,上次备份是当天0点
2 用备份恢复出一个临时库
3 从日志备份里面,取出凌晨0点之后的日志
4 把这些日志,除了误删除数据的语句外,全部应用到临时库
--说明
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解析出日志应用,应用日志的过程就只能是单线程。
一种加速的方法是,在用备份恢复出临时实例后,将这个临时实例设置成线上线上备库的从库,这样
在start slave之前,先通过执行:
--1 change replication filter replicate_do_table = (table_name),命令,就可以让临时库只同步误操作的表。
--2 这样做也可以用到并行复制,来加速整个数据恢复的过程。
假设,我们发现当前临时实例需要的binlog从master.000005开始,但是在备库执行show binlogs显示的最小的binlog文件是master.000007,意味着少了两个binlog文件,这时,就需要去binlog备份系统中找到这两个文件。
把之前删掉的binlog放回备库的操作:
--1 从备份系统下载master.000005和master.000006这两个文件,放到备库的日志目录下
--2 打开日志目录文件master.index,在文件开头加入两行,内容分表是”./master.000005”和”./master.000006”
--3 重启备库,目的是要让备库重新识别这2个日志文件
--4 现在这个备库已经有临时库需要的所有binlog,建立主备关系,就可以正常同步。
上面两种方式恢复,主要的思路是:通过备份,加上应用binlog的方式。
但是,一个系统不可能备份无限的日志,还需要根据成本和磁盘空间资源,设定一个日志的保留天数,或者告知需要保存某个实例恢复到半个月内的任意时间,就表示备份系统保留的日志时间就至少是半个月。
延迟复制备库
一般的主备复制结构存在的问题是,如果主库上有个表被删除了,这个命令会很快发给从库,进而导致所有从库的数据表也一起被删除掉,延迟复制的备库是一种特殊的备库,通过change master to master_delay=n 命令,可以指定这个备库持续保持跟主库的有n秒的延时。
如果把n设置为3600,就代表如果主库上有数据被误删了,并且在1个小时发现了这个误命令,在延迟备库上,执行stop slave,然后跳过误操作的命令,就可以恢复数据。
预防误删库/表的方法
建议:
--1 账号分离,避免写错命令
---我们只给业务开发同学DML权限,而不给truncate/drop权限,而如果业务开发人员有DDL需求的话,也可以通过开发管理系统得到支持。
---即使dba成员,日常也都有规定只使用只读账号,必要的时候才使用有更新权限的账号
--2制定操作规范,避免写错要删除的表名
---在删除数据表之前,必须先对表做改名操作,然后,观察一段时间,确保对业务无影响以后再删除这张表。
---改表名的时候,要求给表名加固定的后缀,比如to_be_deleted,然后删除表的动作必须通过管理系统执行,并且管理系统删除表的时候,而能删除固定后缀的表。
rm 删除数据
对于一个有高可用机制的mysql集群来说,只要不是恶意把整个集群删除,而是删除其中某一个节点的数据的话,ha系统就会开始工作,选出一个新的主库,从而保证整个集群的正常工作。
现在不止是DBA有自动化系统,SA(系统管理员)也有自动管理系统,建议就是尽量把备份跨机房,或者跨城市。
小结
强调的是,预防远比处理的意义来得更大
另外,在mysql的集群方案中,会时不时得用到备份来恢复实例,因此定期检查备份的有效性也很有必要,如果是业务开发,可以用show grants命令查看账户的权限,如果权限过大,可以建议分配权限低一些的账号,也可以评估业务的重要性,和dba商量备份的周期,是否有必要创建延迟复制的备库等等。
数据和服务的可靠性不止是运维团队的工作,最终是各个环节一起保障的结果。
关于空表的间隙的定义
一个空表就只有一个间隙,在空表上执行
begin; select * from t where id>1 for update;
这个查询语句加锁的范围是next-key lock(-无穷,supermum]
CREATE TABLE `t31` ( `id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(system@127.0.0.1:3306) [test]> SET SESSION tx_isolation='REPEATABLE-READ';
SESSION A |
SESSION B |
begin; select * from t31 where id>1 for update; |
|
insert into t31 values(2);(blocked) ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
--show engine innodb stautsG;