MySQL 数据恢复的多种方法汇总


Posted in MySQL onJune 21, 2021
目录
  • 1、前言
  • 2、直接恢复
    • 2.1 mysqldump 备份全量恢复
    • 2.2 xtrabackup 备份全量恢复
    • 2.3 基于时间点恢复
  • 3、恢复一个表
    • 3.1 从 mysqldump 备份恢复一个表
    • 3.2 从 xtrabackup 备份恢复一个表
  • 4、跳过误操作SQL
    • 4.1 使用备份文件恢复跳过
    • 4.2 使用延迟库跳过
  • 5. 闪回。
    • 5.1 binlog2sql
    • 5.2 MyFlash

 

1、前言

数据恢复的前提的做好备份,且开启 binlog,格式为 row。如果没有备份文件,那么删掉库表后就真的删掉了,lsof 中还有记录的话,有可能恢复一部分文件。但若刚好数据库没有打开这个表文件,那就只能跑路了。如果没有开启 binlog,那么恢复数据后,从备份时间点开始的数据都没了。如果 binlog 格式不为 row,那么在误操作数据后就没有办法做闪回操作,只能老老实实地走备份恢复流程。

 

2、直接恢复

直接恢复是使用备份文件做全量恢复,这是最常见的场景。

 

2.1 mysqldump 备份全量恢复

使用 mysqldump 文件恢复数据非常简单,直接解压了执行:

gzip -d backup.sql.gz | mysql -u<user> -h<host> -P<port> -p

 

2.2 xtrabackup 备份全量恢复

恢复过程:

# 步骤一:解压(如果没有压缩可以忽略这一步)
innobackupex --decompress <备份文件所在目录>

# 步骤二:应用日志
innobackupex --apply-log <备份文件所在目录> 

# 步骤三:复制备份文件到数据目录
innobackupex --datadir=<MySQL数据目录> --copy-back <备份文件所在目录>

 

2.3 基于时间点恢复

基于时间点的恢复依赖的是 binlog 日志,需要从 binlog 中找过从备份点到恢复点的所有日志,然后应用。我们测试一下。

新建测试表:

chengqm-3306>>show create table mytest.mytest \G;
*************************** 1. row ***************************
       Table: mytest
Create Table: CREATE TABLE `mytest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ctime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

每秒插入一条数据:

[mysql@mysql-test ~]$ while true; do mysql -S /tmp/mysql.sock -e 'insert in

备份:

[mysql@mysql-test ~]$ mysqldump --opt --single-transaction --master-data=2 --defa

找出备份时的日志位置:

[mysql@mysql-test ~]$ head -n 25 backup.sql | grep 'CHANGE MASTER TO MASTER_LOG_FILE'
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000032', MASTER_LOG_POS=39654;

假设要恢复到 2019-08-09 11:01:54 这个时间点,我们从 binlog 中查找从 39654 到 019-08-09 11:01:54 的日志。

[mysql@mysql-test ~]$ mysqlbinlog --start-position=39654 --stop-datetime='2019-08-09 11:01:54' /data/mysql_log/mysql_test/mysql-bin.000032 > backup_inc.sql
[mysql@mysql-test-83 ~]$ tail -n 20 backup_inc.sql
......
### INSERT INTO `mytest`.`mytest`
### SET
###   @1=161 /* INT meta=0 nullable=0 is_null=0 */
###   @2='2019-08-09 11:01:53' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
......

当前数据条目数:

-- 2019-08-09 11:01:54之前的数据条数
chengqm-3306>>select count(*) from mytest.mytest where ctime < '2019-08-09 11:01:54';
+----------+
| count(*) |
+----------+
|      161 |
+----------+
1 row in set (0.00 sec)

所有数据条数

chengqm-3306>>select count(*) from mytest.mytest;
+----------+
| count(*) |
+----------+
|      180 |
+----------+
1 row in set (0.00 sec)

然后执行恢复:

# 全量恢复
[mysql@mysql-test ~]$ mysql -S /tmp/mysql.sock < backup.sql 

# 应用增量日志
[mysql@mysql-test ~]$ mysql -S /tmp/mysql.sock < backup_inc.sql

检查数据:

chengqm-3306>>select count(*) from mytest.mytest;
+----------+
| count(*) |
+----------+
|      161 |
+----------+
1 row in set (0.00 sec)

chengqm-3306>>select * from mytest.mytest order by id desc limit 5;
+-----+---------------------+
| id  | ctime               |
+-----+---------------------+
| 161 | 2019-08-09 11:01:53 |
| 160 | 2019-08-09 11:01:52 |
| 159 | 2019-08-09 11:01:51 |
| 158 | 2019-08-09 11:01:50 |
| 157 | 2019-08-09 11:01:49 |
+-----+---------------------+
5 rows in set (0.00 sec)

已经恢复到 2019-08-09 11:01:54 这个时间点。

 

3、恢复一个表

 

3.1 从 mysqldump 备份恢复一个表

假设要恢复的表是 mytest.mytest:

# 提取某个库的所有数据
sed -n '/^-- Current Database: `mytest`/,/^-- Current Database:/p' backup.sql > backup_mytest.sql

# 从库备份文件中提取建表语句
sed -e'/./{H;$!d;}' -e 'x;/CREATE TABLE `mytest`/!d;q' backup_mytest.sql > mytest_table_create.sql

# 从库备份文件中提取插入数据语句
grep -i 'INSERT INTO `mytest`' backup_mytest.sql > mytest_table_insert.sql

# 恢复表结构到 mytest 库
mysql -u<user> -p mytest < mytest_table_create.sql

# 恢复表数据到 mytest.mytest 表
mysql -u<user> -p mytest <  mytest_table_insert.sql

 

3.2 从 xtrabackup 备份恢复一个表

假设 ./backup_xtra_full 目录为解压后应用过日志的备份文件。

3.2.1 MyISAM 表
假设从备份文件中恢复表 mytest.t_myisam。从备份文件中找到 t_myisam.frm, t_myisam.MYD, t_myisam.MYI 这 3 个文件,复制到对应的数据目录中,并授权

进入 MySQL。检查表情况:

chengqm-3306>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| mytest           |
| t_myisam         |
+------------------+
2 rows in set (0.00 sec)

chengqm-3306>>check table t_myisam;
+-----------------+-------+----------+----------+
| Table           | Op    | Msg_type | Msg_text |
+-----------------+-------+----------+----------+
| mytest.t_myisam | check | status   | OK       |
+-----------------+-------+----------+----------+
1 row in set (0.00 sec)

3.2.2 Innodb 表
假设从备份文件中恢复表 mytest.t_innodb,恢复前提是设置了 innodb_file_per_table = on:

  • 起一个新实例;
  • 在实例上建一个和原来一模一样的表;
  • 执行 alter table t_innodb discard tablespace; 删除表空间,这个操作会把 t_innodb.ibd 删除;
  • 从备份文件中找到 t_innodb.ibd 这个文件,复制到对应的数据目录,并授权;
  • 执行 alter table t_innodb IMPORT tablespace; 加载表空间;
  • 执行 flush table t_innodb;check table t_innodb; 检查表;
  • 使用 mysqldump 导出数据,然后再导入到要恢复的数据库。

注意:

在新实例上恢复再 dump 出来是为了避免风险,如果是测试,可以直接在原库上操作步骤 2-6;
只在 8.0 以前的版本有效。

 

4、跳过误操作SQL

跳过误操作 SQL 一般用于执行了无法闪回的操作比如 drop table\database。

 

4.1 使用备份文件恢复跳过

4.1.1 不开启 GTID

使用备份文件恢复的步骤和基于时间点恢复的操作差不多,区别在于多一个查找 binlog 操作。举个例子,我这里建立了两个表 a 和 b,每分钟插入一条数据,然后做全量备份,再删除表 b,现在要跳过这条 SQL。

删除表 b 后的数据库状态:

chgnqm-3306>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| a                |
+------------------+
1 row in set (0.00 sec)

 找出备份时的日志位置

[mysql@mysql-test ~]$ head -n 25 backup.sql | grep 'CHANGE MASTER TO MASTER_LOG_FILE'
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000034', MASTER_LOG_POS=38414;

找出执行了 drop table 语句的 pos 位置

[mysql@mysql-test mysql_test]$  mysqlbinlog -vv /data/mysql_log/mysql_test/mysql-bin.000034 | grep -i -B 3 'drop table `b`';
# at 120629
#190818 19:48:30 server id 83  end_log_pos 120747 CRC32 0x6dd6ab2a     Query    thread_id=29488    exec_time=0    error_code=0
SET TIMESTAMP=1566128910/*!*/;
DROP TABLE `b` /* generated by server */

从结果中我们可以看到 drop 所在语句的开始位置是 120629,结束位置是 120747。

从 binglog 中提取跳过这条语句的其他记录

# 第一条的 start-position 为备份文件的 pos 位置,stop-position 为 drop 语句的开始位置
mysqlbinlog -vv --start-position=38414 --stop-position=120629 /data/mysql_log/mysql_test/mysql-bin.000034 > backup_inc_1.sql

# 第二条的 start-position 为 drop 语句的结束位置
mysqlbinlog -vv --start-position=120747 /data/mysql_log/mysql_test/mysql-bin.00003

恢复备份文件

[mysql@mysql-test ~]$ mysql -S /tmp/mysql.sock < backup.sql

全量恢复后状态:

chgnqm-3306>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| a                |
| b                |
+------------------+
2 rows in set (0.00 sec)

chgnqm-3306>>select count(*) from a;
+----------+
| count(*) |
+----------+
|       71 |
+----------+
1 row in set (0.00 sec)

恢复增量数据

[mysql@mysql-test ~]$ mysql -S /tmp/mysql.sock < backup_inc_1.sql
[mysql@mysql-test ~]$ mysql -S /tmp/mysql.sock < backup_inc_2.sql

恢复后状态,可以看到已经跳过了 drop 语句:

chgnqm-3306>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| a                |
| b                |
+------------------+
2 rows in set (0.00 sec)

chgnqm-3306>>select count(*) from a;
+----------+
| count(*) |
+----------+
|      274 |
+----------+
1 row in set (0.00 sec)

4.1.2 开启 GTID
使用 GTID 可以直接跳过错误的 SQL:

  • 找出备份时的日志位置;
  • 找出执行了 drop table 语句的 GTID 值;
  • 导出备份时日志位置到最新的 binglog 日志;
  • 恢复备份文件;
  • 跳过这个 GTID;
SET SESSION GTID_NEXT='对应的 GTID 值';
BEGIN; COMMIT;
SET SESSION GTID_NEXT = AUTOMATIC;

应用步骤 3 得到的增量 binlog 日志。

 

4.2 使用延迟库跳过

4.2.1 不开启 GTID

使用延迟库恢复的关键操作在于 start slave until。我在测试环境搭建了两个 MySQL 节点,节点二延迟600秒,新建 a,b 两个表,每秒插入一条数据模拟业务数据插入。

localhost:3306 -> localhost:3307(delay 600)

当前节点二状态:

chengqm-3307>>show slave status \G;
...
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000039
          Read_Master_Log_Pos: 15524
               Relay_Log_File: mysql-relay-bin.000002
                Relay_Log_Pos: 22845
        Relay_Master_Log_File: mysql-bin.000038
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
        Seconds_Behind_Master: 600
...

当前节点二表:

chengqm-3307>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| a                |
| b                |
+------------------+

在节点一删除表 b:

chengqm-3306>>drop table b;
Query OK, 0 rows affected (0.00 sec)

chengqm-3306>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| a                |
+------------------+
1 row in set (0.00 sec)

接下来就是跳过这条 SQL 的操作步骤。

延迟库停止同步

stop slave;

找出执行了 drop table 语句的前一句的 pos 位置

[mysql@mysql-test ~]$ mysqlbinlog -vv /data/mysql_log/mysql_test/mysql-bin.000039 | grep -i -B 10 'drop table `b`';
...
# at 35134
#190819 11:40:25 server id 83  end_log_pos 35199 CRC32 0x02771167     Anonymous_GTID    last_committed=132    sequence_number=133    rbr_only=no
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 35199
#190819 11:40:25 server id 83  end_log_pos 35317 CRC32 0x50a018aa     Query    thread_id=37155    exec_time=0    error_code=0
use `mytest`/*!*/;
SET TIMESTAMP=1566186025/*!*/;
DROP TABLE `b` /* generated by server */

从结果中我们可以看到 drop 所在语句的前一句开始位置是 35134,所以我们同步到 35134(这个可别选错了)。

延迟库同步到要跳过的 SQL 前一条

change master to master_delay=0;
start slave until master_log_file='mysql-bin.000039',master_log_pos=35134;

查看状态看到已经同步到对应节点:

chengqm-3307>>show slave status \G;
...
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000039
          Read_Master_Log_Pos: 65792
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
          Exec_Master_Log_Pos: 35134
...
               Until_Log_File: mysql-bin.000039
                Until_Log_Pos: 35134

跳过一条 SQL 后开始同步

set global sql_slave_skip_counter=1;
start slave;

查看同步状态,删除表 b 的语句已经被跳过:

chengqm-3307>>show slave status \G;
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
...
1 row in set (0.00 sec)

chengqm-3307>>show tables;
+------------------+
| Tables_in_mytest |
+------------------+
| a                |
| b                |
+------------------+
2 rows in set (0.00 sec)

4.2.2 开启 GTID
使用 GTID 跳过的步骤会简单很多,只要执行一条和要跳过的 SQL 的 GTID 相同的事务就可以跳过了。

  • 停止同步;
  • 找出执行了 drop table 语句的 GTID;
  • 执行这个 GTID 的事务;
SET SESSION GTID_NEXT='对应的 GTID 值';
BEGIN; COMMIT;
SET SESSION GTID_NEXT = AUTOMATIC;
  • 继续同步;

 

5. 闪回。

闪回操作就是反向操作,比如执行了 delete from a where id=1,闪回就会执行对应的插入操作 insert into a (id,...) values(1,...),用于误操作数据,只对 DML 语句有效,且要求 binlog 格式设为 ROW。本章介绍两个比较好用的开源工具。

 

5.1 binlog2sql

binlog2sql 是大众点评开源的一款用于解析 binlog 的工具,可以用于生成闪回语句,项目地址 binlog2sql。

5.1.1 安装

wget https://github.com/danfengcao/binlog2sql/archive/master.zip -O binlog2sql.zip
unzip binlog2sql.zip
cd binlog2sql-master/

# 安装依赖
pip install -r requirements.txt

5.1.2 生成回滚SQL

python binlog2sql/binlog2sql.py --flashback \
-h<host> -P<port> -u<user> -p'<password>' -d<dbname> -t<table_name>\
--start-file='<binlog_file>' \
--start-datetime='<start_time>' \
--stop-datetime='<stop_time>' > ./flashback.sql

python binlog2sql/binlog2sql.py --flashback \
-h<host> -P<port> -u<user> -p'<password>' -d<dbname> -t<table_name> \
--start-file='<binlog_file>' \
--start-position=<start_pos> \
--stop-position=<stop_pos> > ./flashback.sql

 

5.2 MyFlash

MyFlash 是由美团点评公司技术工程部开发维护的一个回滚 DML 操作的工具,项目链接 MyFlash。

限制:

  • binlog 格式必须为 row,且 binlog_row_image=full;
  • 仅支持5.6与5.7;
  • 只能回滚 DML(增、删、改)。

5.2.1 安装

# 依赖(centos)
yum install gcc*  pkg-config glib2 libgnomeui-devel -y

# 下载文件
wget https://github.com/Meituan-Dianping/MyFlash/archive/master.zip -O MyFlash.zip
unzip MyFlash.zip
cd MyFlash-master

# 编译安装
gcc -w  `pkg-config --cflags --libs glib-2.0` source/binlogParseGlib.c  -o binary/flashback
mv binary /usr/local/MyFlash
ln -s /usr/local/MyFlash/flashback /usr/bin/flashback

5.2.2 使用
生成回滚语句:

flashback --databaseNames=<dbname> --binlogFileNames=<binlog_file> --start-position=<s

执行后会生成 binlog_output_base.flashback 文件,需要用 mysqlbinlog 解析出来再使用:

mysqlbinlog -vv binlog_output_base.flashback | mysql -u<user> -p

以上就是MySQL 数据恢复的多种方法汇总的详细内容,更多关于MySQL 数据恢复的资料请关注三水点靠木其它相关文章!

MySQL 相关文章推荐
MySQL Shell的介绍以及安装
Apr 24 MySQL
MySQL获取所有分类的前N条记录
May 07 MySQL
mysql对于模糊查询like的一些汇总
May 09 MySQL
MySQL表字段时间设置默认值
May 13 MySQL
python中的mysql数据库LIKE操作符详解
Jul 01 MySQL
JMeter对MySQL数据库进行压力测试的实现步骤
Jan 22 MySQL
mysql分组后合并显示一个字段的多条数据方式
Jan 22 MySQL
MySQL日期时间函数知识汇总
Mar 17 MySQL
解决Mysql中的innoDB幻读问题
Apr 29 MySQL
MySQL数据库安装方法与图形化管理工具介绍
May 30 MySQL
MySQL中order by的执行过程
Jun 05 MySQL
Mysql数据库值的添加、修改、删除及清空操作实例
Unity连接MySQL并读取表格数据的实现代码
新手入门Mysql--sql执行过程
MySQL 外键约束和表关系相关总结
Jun 20 #MySQL
MySQL 使用索引扫描进行排序
Jun 20 #MySQL
新手入门Mysql--概念
mysql获取指定时间段中所有日期或月份的语句(不设存储过程,不加表)
You might like
PHP 高级课程笔记 面向对象
2009/06/21 PHP
smarty模板中使用get、post、request、cookies、session变量的方法
2014/04/24 PHP
PHP查看SSL证书信息的方法
2016/09/22 PHP
PHP 7.0新增加的特性介绍
2017/06/08 PHP
万能的php分页类
2017/07/06 PHP
PHP+Apache实现二级域名之间共享cookie的方法
2019/07/24 PHP
[对联广告] JS脚本类
2006/08/27 Javascript
一个XML格式数据转换为图表的例子
2010/02/09 Javascript
JS target与currentTarget区别说明
2011/08/28 Javascript
基于SVG的web页面图形绘制API介绍及编程演示
2013/06/28 Javascript
javascript判断两个IP地址是否在同一个网段的实现思路
2013/12/13 Javascript
jquery和javascript中如何将一元素的内容赋给另一元素
2014/01/09 Javascript
js中创建对象的几种方式示例介绍
2014/01/26 Javascript
在线引用最新jquery文件的实现方法
2016/08/26 Javascript
解决vue页面刷新或者后退参数丢失的问题
2018/03/13 Javascript
Vue 之孙组件向爷组件通信的实现
2019/04/23 Javascript
vue2 v-model/v-text 中使用过滤器的方法示例
2019/05/09 Javascript
Python中非常实用的一些功能和函数分享
2015/02/14 Python
python中如何正确使用正则表达式的详细模式(Verbose mode expression)
2017/11/08 Python
Python中的单行、多行、中文注释方法
2018/07/19 Python
pytorch打印网络结构的实例
2019/08/19 Python
flask实现验证码并验证功能
2019/12/05 Python
Python高并发和多线程有什么关系
2020/11/14 Python
Trunki英国官网:儿童坐骑式行李箱
2017/05/30 全球购物
DJI全球:DJI Global
2021/03/15 全球购物
商务日语专业毕业生求职信
2013/10/26 职场文书
副总经理工作职责
2013/11/28 职场文书
总经理司机职责
2014/02/02 职场文书
机械系毕业生求职信
2014/05/28 职场文书
公司门卫岗位职责范本
2014/07/08 职场文书
服务明星事迹材料
2014/12/29 职场文书
义诊活动总结
2015/02/04 职场文书
趣味运动会新闻稿
2015/07/17 职场文书
SpringBoot集成Redis,并自定义对象序列化操作
2021/06/22 Java/Android
CSS实现两列布局的N种方法
2021/08/02 HTML / CSS
JS创建或填充任意长度数组的小技巧汇总
2021/10/24 Javascript