概述
MySQL 针对 Drop、Truncate 等 DDL 操作,只能进行备份还原。若无备份情况下,基本上数据无法找回。
某个客户误删过两次数据,第一次是误删表,第二次是误删数据库。
- 第一次误删表恢复接近花费了5-6个小时
- 第二次误删库,恢复花费接近 3 天。这如果是核心数据,这个恢复时间其实是有点难以接受的。
在此背景下,我们思考 MySQL 如何提高 RTO 。
下面先从技术层面分析误删的场景以及对应方式
针对无备份下的 DDL 场景,还可以使用 TwinsDB 等开源工具,直接分析数据文件进行恢复,不过此时的恢复已经无法完全保障,而且必须要保证数据文件还在文件系统缓存中,如果被覆盖或者清理掉,那就只能做备份还原。
应对如上问题,今天主要介绍 MySQL 延迟复制 + 快照实快速速数据恢复。主要恢复思路如下:
前提条件:
- 存在一个延迟从库,延迟时间要大于已删除数据库的时间,如当前时间是 9:00,客户8:00删掉了一个库,那么延迟阈值至少要大于1个小时;
恢复思路:
- 主库半同步降级
- 停止延迟从库复制
- 给延迟从库打一个虚拟机快照
- 找到需要恢复的 GTID 或 Position
- 基于 GTID 或 Position 将延迟从库复制追到故障点之前
- 导出被误删的表或库,并发送给客户确认
- 在客户确认数据无误后,做快照恢复,恢复至延迟从库停止复制那一刻
- 正常启动延迟复制
- 删除快照
- 主库还原半同步设置
注意:如上操作,除了主库半同步降级以外,没有一个是在主库上进行的;所有操作均在延迟从库执行。
相比传统的 PITR,MySQL 在追日志阶段是单线程重放,使用复制模式,可以使用并行重放,所以会提高恢复效率;并行会发行只有 GTID 才支持,若基于 Position 模式,并行将失效。
# 可以并行
start slave until sql_before_gtids="e29fc82c-e336-11ed-b5ed-fefcfedd3ddb:745";
# 不可并行
start slave until master_log_file='mysql-bin.000005',master_log_pos=1181;
如果发现客户开启了 GTID,建议使用 GTID 模式做重放。
原理
延迟从库其实是放慢 SQL Thread 线程的重放,也就是说主库产生的 binlog 会源源不断的送到从库,只是从库会延迟重放这些日志。
实验
开启延迟复制
change master to
master_host='10.5.54.10',
master_user='repl',
master_password='Qwer@123',
master_port=3306,
master_log_file='mysql-bin.000001',
master_log_pos=155,
master_delay=28800;
也可以针对已运行的从库设置
stop slave;
change master to master_delay = 28800;
start slave;
延迟复制相关信息通过 show slave status\G 查看
SQL_Delay: 28800
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
使用延迟复制恢复误删的表
下面将使用已存在的从库设置延迟从库,来恢复误删的表;
0、数据准备
主库创建测试数据
create database test;
create table test.t1(id int primary key);
insert into test.t1 values(1),(2);
select * from test.t1;
在从库中检查数据是否有同步过来
mysql> select * from test.t1;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)
1、DBVM 进入维护模式
2、主库半同步降级
mysql> set global rpl_semi_sync_master_timeout = 0;
Query OK, 0 rows affected (0.00 sec)
3、从库开启延迟复制
stop slave;
change master to master_delay = 28800;
start slave;
延迟 28800 秒,即 8 小时。
4、在主库中新增一条记录,并删除 t1 表。
insert into test.t1 values(3);
drop table test.t1;
此时查看从库,会发现 t1 表依然存在,且只有两条记录。
mysql> select * from test.t1;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.00 sec)
5、在主库中,查看 DROP 操作在 binlog 中的位置点信息。
mysql> show master status\G
*************************** 1. row ***************************
File: mysql-bin.000005
Position: 1364
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 3e567c47-de8b-11ed-923a-fefcfee63601:1-88,
d7c72e02-de8a-11ed-a53c-fefcfe9ec167:1-12
1 row in set (0.00 sec)
mysql>
mysql> pager grep -iB 5 drop
PAGER set to 'grep -iB 5 drop'
mysql> show binlog events in 'mysql-bin.000005';
| mysql-bin.000005 | 1012 | Rows_query | 1226 | 1065 | # insert into test.t1 values(3) |
| mysql-bin.000005 | 1065 | Table_map | 1226 | 1110 | table_id: 583 (test.t1) |
| mysql-bin.000005 | 1110 | Write_rows | 1226 | 1150 | table_id: 583 flags: STMT_END_F |
| mysql-bin.000005 | 1150 | Xid | 1226 | 1181 | COMMIT /* xid=3583205 */ |
| mysql-bin.000005 | 1181 | Gtid | 1226 | 1246 | SET @@SESSION.GTID_NEXT= '3e567c47-de8b-11ed-923a-fefcfee63601:88' |
| mysql-bin.000005 | 1246 | Query | 1226 | 1364 | DROP TABLE `test`.`t1` /* generated by server */ |
20 rows in set (0.00 sec)
6、将从库恢复到指定位置点
6.1 停止同步
stop slave;
6.2 给从库打个快照
6.3 将从库恢复到指定点,指定到上一个事务的 begin_pos。
# GTID 模式
start slave until sql_before_gtids="3e567c47-de8b-11ed-923a-fefcfee63601:88";
# Position 模式
change master to master_delay = 0;
start slave until master_log_file='mysql-bin.000005',master_log_pos=1181;
注意:GTID 模式下,可以指定当前 GTID,因为我们使用的是 before_gtid,他将不会执行当前的 GTID;若是 Position 模式,需要指定上一条语句的begin_positioin,也就是 Pos(第二列) 那一列。
7、从库检查执行情况
show slave status\G
确保(Relay_Master_Log_File = Until_log_File) && (Exec_Master_Log_Pos = Until_Log_Pos) && (Slave_Sql_Running = 'No')
8、查看 t1 表的内容
mysql> select * from test.t1;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
3 rows in set (0.00 sec)
9、导出表给到客户,待客户确认
mysqldump -uroot -p -h127.0.0.1 --set-gtid-purged=OFF --default-character-set=utf8mb4 --hex-blob test t1 > test.t1.sql
10、确认无误后,延迟从库做快照还原,具体操作根据自身环境中的云能力。
11、还原延迟从库
11.1 启动数据库
systemctl restart mysqld
11.2 启动延迟复制
change master to master_delay = 28800;
start slave;
11.3 查看数据
mysql> select * from test.t1;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.08 sec)
11.4 查看复制状态
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.5.54.9
Master_User: sangforroot_repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000005
Read_Master_Log_Pos: 1364
Relay_Log_File: host-fefcfe9ec167-relay-bin.000002
Relay_Log_Pos: 414
Relay_Master_Log_File: mysql-bin.000005
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 879
Relay_Log_Space: 1118
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 1898
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1226
Master_UUID: 3e567c47-de8b-11ed-923a-fefcfee63601
Master_Info_File: mysql.slave_master_info
SQL_Delay: 28800
SQL_Remaining_Delay: 26902
Slave_SQL_Running_State: Waiting until MASTER_DELAY seconds after master executed event
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: 3e567c47-de8b-11ed-923a-fefcfee63601:87-88
Executed_Gtid_Set: 3e567c47-de8b-11ed-923a-fefcfee63601:1-86,
d7c72e02-de8a-11ed-a53c-fefcfe9ec167:1-12
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
12、删除快照
13、主库还原半同步设置