【BUG 记录】史诗级 BUG - MYSQL 删库删表却没有备份如何恢复数据

在这里插入图片描述

1. 问题描述


  在实际开发过程中,不小心把测试环境的三张业务表(usercomanyuser_dept_rel)数据删除了,并且当时没有备份这三张表的数据,导致测试环境瘫痪,其中最关键的user表给删了,导致很多测试账号登录不上系统,搞了一下午,最终通过binlog日志 恢复了数据。在此记录一下当时的解决过程。

环境:Python 3.11
mysql:8.0+

2. 解决方案(binlog)

2.1 构造测试环境


  1. 库(test

  2. 建表(usercompanyuser_dept_rel),面下测试中主要使用user表来演示

    create table user(
    	id int AUTO_INCREMENT primary key,
    	name varchar(50),
    	phone varchar(50)
    );
    
    insert into user(id,name,phone) value(1,"zhangsan",17756566565);
    insert into user(id,name,phone) value(2,"lisi",17712345687);
    insert into user(id,name,phone) value(3,"wangwu",17756562333);
    insert into user(id,name,phone) value(4,"zhaoliu",17756564444);
    
    create table company(
    	id int AUTO_INCREMENT primary key,
    	name varchar(50),
    	code varchar(50)
    );
    
    insert into company(id,name,code) value(1,"测试公司1","ceshigongsi1");
    insert into company(id,name,code) value(2,"测试公司2","ceshigongsi2");
    insert into company(id,name,code) value(3,"测试公司3","ceshigongsi3");
    insert into company(id,name,code) value(4,"测试公司4","ceshigongsi4");
    
    create table user_dept_rel(
    	id int AUTO_INCREMENT primary key,
    	user_id int,
    	dept_id int
    );
    
    insert into user_dept_rel(id,user_id,dept_id) value(1,111,1001);
    insert into user_dept_rel(id,user_id,dept_id) value(2,222,1002);
    insert into user_dept_rel(id,user_id,dept_id) value(3,333,1003);
    insert into user_dept_rel(id,user_id,dept_id) value(4,444,1004);
    
  3. 查看数据
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.2 查看 MySQL 环境是否开启 binlog


  通过命令查看是否开启 binlog 记录功能(该功能,默认是开启的)

show variables like '%log_bin%';

在这里插入图片描述

2.3 查看所有的 binlog 日志记录


  1. 命令
    show binary logs;
    
  2. 结果
    在这里插入图片描述

2.4 查看当前正在使用的是哪一个 binlog 文件


  1. 命令
    -- 查看当前的 binlog 文件
    show master status;
    
  2. 结果
    在这里插入图片描述
    当使用的是MYSQL-bin.000042这个日志文件,在前面 binlog 日志文件路径 (我的是D:\Settings\MySQL\DATA\Data )下查看
    在这里插入图片描述

2.5 查看此时的 binlog 日志记录


  1. 命令

    show binlog events in 'MYSQL-bin.000042';
    
  2. 结果

    在这里插入图片描述

2.6 删除记录(这里模拟删除表操作)


  1. 删除
    delete from test.user;
    delete from test.company;
    delete from test.user_dept_rel;
    
  2. 查看日志
    在这里插入图片描述
    可以看到我们三个删除事件,注意binlog 中每个事件都有一个 begincommit,我们后面进行恢复或回滚的时候开始和结束的 pos 都是取的事件整体的开始点和结束点。比如上面第一个删除事件的开始点(Pos)其实是7868,结束点(End_log_pos)则是8167

2.7 mysqlbinlog 恢复数据


注意: 这里是恢复数据,不是回滚数据

  • 恢复的本质:将原有的插入语句再执行一遍
  • 回滚的本质:回退到删除之前的状态

  mysqlbinlogmysql自带的命令,一般是在mysql安装目录下的bin目录里。因为我们是恢复数据,所以要找到已经删除语句的对应写入事件,将该事件再重新执行一遍即可

  1. 通过命令确认插入语句的事件位置

    show binlog events in 'MYSQL-bin.000042';
    

    在这里插入图片描述
    确定了 user 表的插入事件起始位置是3677 ,结果位置是4819

  2. 使用 mysqlbinlog 命令恢复

    D:\Settings\MySQL\DATA\Data>mysqlbinlog.exe --start-position=3677 --stop-position=4819 mysql-bin.000042 | mysql -uroot -p123456
    

    常用参数解释
    D:\Settings\MySQL\DATA\Data> : mysqlbinlog 的路径,就上面通过语句查出来的日志路径
    --start-position:起始位置
    --stop-position:结束位置
    --start-datetime:起始日期
    --stop-datetime:结束日期
    mysql-bin.000042:日志文件

  3. 查看结果
    在这里插入图片描述

    • 查看表:在这里插入图片描述
      可以看到数据已经回来了

    • 查看 binlog 日志:
      在这里插入图片描述
      可以看出,日志中也多了四条写入事件。这里只恢复了user表,companyuser_dept 同理

注意如果说找不到 insert 语句了,或者insert 语句 在很久之前执行的,找不到,我们可以通过在日志中找出它的 delete 语句,然后手动将 delete from 语句转换成 insert 语句(可以写一个python 脚本),重新执行一下就可以了。(我当时就是这么干的)

小结

  • mysqlbinlog命令只是用于恢复,不能用于回滚。如果数据进行update操作,则很难通过该命令恢复。所以该命令比较适用一些数据迁移,数据同步的场景。

  • mysqlbinlog 运行过程中如果出现unknown variable 'default-character-set=utf8mb4'异常,可以再该命令后加--no-defaults参数解决:mysqlbinlog --no-defaults

2.8 binlog2sql 数据回滚


  binlog2sql是一个第三方的工具,binlog2sql回滚的原理是生成要回滚事件对应的sql语句,我们最后只需要拷贝该语句实现即可。

3. binlog2sql 详细介绍

3.1 概念


  binlog2sql是一个用于解析二进制日志的开源工具。它具有从二进制日志中提取原始 SQL 语句的功能。它具有从二进制日志生成回滚 SQL 以进行时间点恢复的功能。

3.2 安装 binlog2sql


前提条件:安装这个工具,需要电脑具备python环境,如果没有还请自行安装python 3.11 (这块我的python版本是3.11)

cd D:\Workspace\PyCharm\
git clone https://github.com/danfengcao/binlog2sql.git && cd binlog2sql
pip install -r requirements.txt

3.3 更改mysql配置文件


  在 my.ini 中添加,找到log-bin="MYSQL-bin",然后在下一行添加

log-bin
server_id = 1
binlog_format = row
binlog_row_image = full

在这里插入图片描述

~修改完之后重启 mysql 服务

3.4 运行脚本获取数据库里的内容


D:\Workspace\PyCharm\binlog2sql>cd binlog2sql
D:\Workspace\PyCharm\binlog2sql\binlog2sql>python binlog2sql.py -uroot -p123456 -P3306 -d test -t user --start-file=D:\Settings\MySQL\DATA\Data\  --stop-file=MYSQL-bin.000042
Traceback (most recent call last):
  File "D:\Workspace\PyCharm\binlog2sql\binlog2sql\binlog2sql.py", line 145, in <module>
    binlog2sql = Binlog2sql(connection_settings=conn_setting, start_file=args.start_file, start_pos=args.start_pos,
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Workspace\PyCharm\binlog2sql\binlog2sql\binlog2sql.py", line 46, in __init__
    self.connection = pymysql.connect(**self.conn_setting)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Settings\Python\Python311\Lib\site-packages\pymysql\__init__.py", line 90, in Connect
    return Connection(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Settings\Python\Python311\Lib\site-packages\pymysql\connections.py", line 706, in __init__
    self.connect()
  File "D:\Settings\Python\Python311\Lib\site-packages\pymysql\connections.py", line 931, in connect
    self._get_server_information()
  File "D:\Settings\Python\Python311\Lib\site-packages\pymysql\connections.py", line 1269, in _get_server_information
    self.server_charset = charset_by_id(lang).name
                          ^^^^^^^^^^^^^^^^^^^
  File "D:\Settings\Python\Python311\Lib\site-packages\pymysql\charset.py", line 38, in by_id
    return self._by_id[id]
           ~~~~~~~~~~~^^^^
KeyError: 255

  如果出现以上报错,更新PyMySQL 即可解决,mysql8的版本问题,安装对应的binlog2sql工具版本

pip uninstall PyMySQL
pip install PyMySQL==0.9.3

再次运行

D:\Workspace\PyCharm\binlog2sql\binlog2sql>python binlog2sql.py -uroot -p123456 -P3306 -d test -t user --start-file=MYSQL-bin.000042  --stop-file=MYSQL-bin.000042
D:\Settings\Python\Python311\Lib\site-packages\pymysql\cursors.py:170: Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 1")
  result = self._query(query)
USE b'test';
create table t1 (
    title varchar(100) default null,
    price int not null) engine=innodb;
USE b'test';
create table user(
        id int AUTO_INCREMENT primary key,
        name varchar(50),
        phone varchar(50)
);
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (1, 'zhangsan', '17756566565'); #start 3598 end 3874 time 2024-02-26 10:55:04
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (2, 'lisi', '17712345687'); #start 3905 end 4177 time 2024-02-26 10:55:04
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (3, 'wangwu', '17756562333'); #start 4208 end 4482 time 2024-02-26 10:55:04
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (4, 'zhaoliu', '17756564444'); #start 4513 end 4788 time 2024-02-26 10:55:04
USE b'test';
create table company(
        id int AUTO_INCREMENT primary key,
        name varchar(50),
        code varchar(50)
);
USE b'test';
create table user_dept_rel(
        id int AUTO_INCREMENT primary key,
        user_id int,
        dept_id int
);
DELETE FROM `test`.`user` WHERE `id`=1 AND `name`='zhangsan' AND `phone`='17756566565' LIMIT 1; #start 7789 end 8136 time 2024-02-26 11:39:35
DELETE FROM `test`.`user` WHERE `id`=2 AND `name`='lisi' AND `phone`='17712345687' LIMIT 1; #start 7789 end 8136 time 2024-02-26 11:39:35
DELETE FROM `test`.`user` WHERE `id`=3 AND `name`='wangwu' AND `phone`='17756562333' LIMIT 1; #start 7789 end 8136 time 2024-02-26 11:39:35
DELETE FROM `test`.`user` WHERE `id`=4 AND `name`='zhaoliu' AND `phone`='17756564444' LIMIT 1; #start 7789 end 8136 time 2024-02-26 11:39:35
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (1, 'zhangsan', '17756566565'); #start 9210 end 9497 time 2024-02-26 10:55:04
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (2, 'lisi', '17712345687'); #start 9528 end 9807 time 2024-02-26 10:55:04
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (3, 'wangwu', '17756562333'); #start 9838 end 10119 time 2024-02-26 10:55:04
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (4, 'zhaoliu', '17756564444'); #start 10150 end 10432 time 2024-02-26 10:55:04

从以上内容能看出来我们刚才所执行的语句,该命令只是将二进制文件解析成我们可读的sql文件。

常用参数解释

-h 主机
-P 端口 
-u 用户名
-p 密码
-d 指定库名
-t 指定表

--start-file   起始解析文件,只需文件名,无需全路径
--start-datetime 起始解析时间,格式'%Y-%m-%d %H:%M:%S'。可选。默认不过滤。
--stop-datetime 终止解析时间,格式'%Y-%m-%d %H:%M:%S'。可选。默认不过滤。

--only-dml 只解析dml,忽略ddl。可选。默认False。
--sql-type 只解析指定类型,支持INSERT, UPDATE, DELETE。多个类型用空格隔开,如--sql-type INSERT DELETE。可选。默认为增删改都解析。用了此参数但没填任何类型,则三者都不解析。

其他参数大家可以自己网上搜索

3.5 删除数据


  由于我们刚才重启了mysql服务,所以当前的binlog日志文件变了
在这里插入图片描述
  现在日志文件是MYSQL-bin.000044

  我们删除数据(这块只删除user表)

delete from user;

  查看日志文件,会多一条delete事件

在这里插入图片描述

3.6 回滚数据


  添加参数 --flashback,生成rollback语句

# 这是没有--flashback 的执行结果,将delete from 语句打印出来
D:\Settings\Python\Python311\Lib\site-packages\pymysql\cursors.py:170: Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 1")
  result = self._query(query)
DELETE FROM `test`.`user` WHERE `id`=1 AND `name`='zhangsan' AND `phone`='17756566565' LIMIT 1; #start 4 end 504 time 2024-02-26 14:22:35
DELETE FROM `test`.`user` WHERE `id`=2 AND `name`='lisi' AND `phone`='17712345687' LIMIT 1; #start 4 end 504 time 2024-02-26 14:22:35
DELETE FROM `test`.`user` WHERE `id`=3 AND `name`='wangwu' AND `phone`='17756562333' LIMIT 1; #start 4 end 504 time 2024-02-26 14:22:35
DELETE FROM `test`.`user` WHERE `id`=4 AND `name`='zhaoliu' AND `phone`='17756564444' LIMIT 1; #start 4 end 504 time 2024-02-26 14:22:35

# 这是添加--flashback 的执行结果,将delete from 语句转换成了insert into 语句 打印出来
D:\Workspace\PyCharm\binlog2sql\binlog2sql>python binlog2sql.py -uroot -p123456 -P3306 -d test -t user --start-file=MYSQL-bin.000044 --flashback
D:\Settings\Python\Python311\Lib\site-packages\pymysql\cursors.py:170: Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 1")
  result = self._query(query)
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (4, 'zhaoliu', '17756564444'); #start 4 end 504 time 2024-02-26 14:22:35
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (3, 'wangwu', '17756562333'); #start 4 end 504 time 2024-02-26 14:22:35
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (2, 'lisi', '17712345687'); #start 4 end 504 time 2024-02-26 14:22:35
INSERT INTO `test`.`user`(`id`, `name`, `phone`) VALUES (1, 'zhangsan', '17756566565'); #start 4 end 504 time 2024-02-26 14:22:35

  我们复制insert into 语句执行就可以了!!!

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JAVA开发区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值