「删库跑路」使用Binlog日志恢复误删的MySQL数据
wptr33 2025-05-03 16:59 24 浏览
前言
“删库跑路”是程序员经常谈起的话题,今天,我就要教大家如何删!库!跑!路!
开个玩笑,今天文章的主题是如何使用Mysql内置的Binlog日志对误删的数据进行恢复,读完本文,你能够了解到:
- MySQL的binlog日志是什么?通常是用来干什么的?
- 模拟一次误删数据的操作,并且使用binlog日志恢复误删的数据。
写这篇文章的初衷,是有一次我真的险些把测试数据库的一张表给删除了,当时吓出一身冷汗。原因是由于Spring JPA的配置中,有一个
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop,其用途是每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。,这个可不能随便配置上去,直接就把你原来存在的表给drop了!
好了,回归正题,这篇文章就是想让大家放心,MySQL就算进行了误删操作,也基本都能够抢救回来。尤其是大公司内,数据可不是你想删就能删掉的,有无数权限/备份阻拦着你。
正文
Binlog介绍
binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。
binlog不会记录SELECT和SHOW这类操作,因为这类操作对数据本身并没有修改,但你可以通过查询通用日志来查看MySQL执行过的所有语句。
看了上面binlog的定义,大家也应该能大致推理出binlog的三大用途:
- 恢复数据:今天要说的重点
- 数据库复制:主从数据库是通过将binlog传给从库,从库有两个线程,一个I/O线程,一个SQL线程,I/O线程读取主库传过来的binlog内容并写入到relay log,SQL线程从relay log里面读取内容,写入从库的数据库。
- 审计:用户可以通过二进制日志中的信息来进行审计,判断是否有对数据库进行注入攻击。
所以说,想要能够恢复数据,首先,你得打开Mysql的binlog,在平常你自己安装的单机Mysql中,默认情况下不会开启。下面就一步步地实践下如何开启你服务器上的Binlog日志。
在MySQL中开启Binlog
首先进入数据库控制台,运行指令:
mysql> show variables like'log_bin%';
+---------------------------------+-------+
| Variable_name | Value |
+---------------------------------+-------+
| log_bin | OFF |
| log_bin_basename | |
| log_bin_index | |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
+---------------------------------+-------+
5 rows in set (0.00 sec)
可以看到我们的binlog是关闭的,都是OFF。接下来我们需要修改Mysql配置文件,执行命令:
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
在文件末尾添加:
log-bin=/var/lib/mysql/mysql-bin
保存文件,重启mysql服务:
sudo service mysql restart
重启完成后,查看下mysql的状态:
systemctl status mysql.service
这时,如果你的mysql版本在5.7或更高版本,就会报错:
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.190791Z 0 [Warning] Changed limits: max_open_files: 1024 (requested 5000)
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.190839Z 0 [Warning] Changed limits: table_open_cache: 431 (requested 2000)
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.359713Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (se
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.361395Z 0 [Note] /usr/sbin/mysqld (mysqld 5.7.28-0ubuntu0.16.04.2-log) starting as process 5930 ...
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.363017Z 0 [ERROR] You have enabled the binary log, but you haven't provided the mandatory server-id. Please refer to the proper server
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.363747Z 0 [ERROR] Aborting
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.363922Z 0 [Note] Binlog end
Jan 06 15:49:58 VM-0-11-ubuntu mysqld[5930]: 2020-01-06T07:49:58.364108Z 0 [Note] /usr/sbin/mysqld: Shutdown complete
Jan 06 15:49:58 VM-0-11-ubuntu systemd[1]: mysql.service: Main process exited, code=exited, status=1/FAILURE
You have enabled the binary log, but you haven't provided the mandatory server-id. Please refer to the proper server
之前我们的配置,对于5.7以下版本应该是可以的。但对于高版本,我们需要指定server-id。
如果你不是分布式的部署Mysql,这个server-id随机给个数字就可以。
server-id=123454
模拟删除数据并恢复
- 首先新建数据库mytest,新建一张表table1,结构见下方SQL代码
CREATE DATABASE `test` ;
USE `test`;
DROP TABLE IF EXISTS `table1`;
CREATE TABLE `table2` (
`id` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 插入两条数据,分别是 (1,'A'),(2,'B')
INSERT INTO `table1` VALUES (1,'A'),(2,'B');
- 我们看一下binlog日志的状态,使用show master status
mysql> show master status
-> ;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 690 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
binlog日志特征:每当我们重启MySQL一次,会自动生成一个binlog文件,当然,我们也可以手动的来刷新binlog文件,通过 flush logs,同样会新创建一个binlog文件。实际上当服务器在重启时,也会调用flush logs操作。
上图代码中可以看到,现在我们正在使用 mysql-bin.0000001 ,并且这个文件现在正在记录到690行。
- 然后,使用flush logs来主动刷新一次binlog
mysql> flush logs;
Query OK, 0 rows affected
mysql> show master status
-> ;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000002 | 154 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
可以看到,现在日志文件在 mysql-bin.000002 文件中,位置为154。也就是我们主动刷新了一次binlog,生成了新的000002,而000001则已经归档了,不会再写入新的日志进去了。
- 接下来我们在插入两条数据
insert into table1 values (3,'C');
insert into table1 values (4,'D');
mysql> select * from table1;
+----+----+
| id |name|
+----+----+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
+----+----+
- 这时候我们已经有了四条数据,我们再次flush logs,把mysql-bin.000002日志存档,开启新的mysql-bin.000003日志,这样,每次我们插入的数据彼此独立。实际情况下,binlog会比较复杂,这里也是做了简化,为了理解更方便。
mysql> flush logs;
Query OK, 0 rows affected
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 | 154 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
- 然后我们删除id为4的数据(4,D),并且再次刷新binlog,如此一来,binlog.000003里面只有一条删除操作。
mysql> delete from table1 where id = 4;
Query OK, 1 row affected
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 | 423 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
mysql> flush logs;
Query OK, 0 rows affected
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000004 | 154 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
- 让我们来好好观察下mysql-bin.00002和mysql-bin00003两个binlog,使用命令:show binlog events in 'mysql-bin.000003'
mysql> show binlog events in 'mysql-bin.000003';
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| mysql-bin.000003 | 4 | Format_desc | 123456 | 123 | Server ver: 5.7.28-0ubuntu0.16.04.2-log, Binlog ver: 4 |
| mysql-bin.000003 | 123 | Previous_gtids | 123456 | 154 | |
| mysql-bin.000003 | 154 | Anonymous_Gtid | 123456 | 219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000003 | 219 | Query | 123456 | 293 | BEGIN |
| mysql-bin.000003 | 293 | Table_map | 123456 | 343 | table_id: 108 (test.table1) |
| mysql-bin.000003 | 343 | Delete_rows | 123456 | 392 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000003 | 392 | Xid | 123456 | 423 | COMMIT /* xid=39 */ |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
7 rows in set
mysql> show binlog events in 'mysql-bin.000002';
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| mysql-bin.000002 | 4 | Format_desc | 123456 | 123 | Server ver: 5.7.28-0ubuntu0.16.04.2-log, Binlog ver: 4 |
| mysql-bin.000002 | 123 | Previous_gtids | 123456 | 154 | |
| mysql-bin.000002 | 154 | Anonymous_Gtid | 123456 | 219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 219 | Query | 123456 | 293 | BEGIN |
| mysql-bin.000002 | 293 | Table_map | 123456 | 343 | table_id: 108 (test.table1) |
| mysql-bin.000002 | 343 | Write_rows | 123456 | 390 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000002 | 390 | Xid | 123456 | 421 | COMMIT /* xid=34 */ |
| mysql-bin.000002 | 421 | Anonymous_Gtid | 123456 | 486 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 486 | Query | 123456 | 560 | BEGIN |
| mysql-bin.000002 | 560 | Table_map | 123456 | 610 | table_id: 108 (test.table1) |
| mysql-bin.000002 | 610 | Write_rows | 123456 | 659 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000002 | 659 | Xid | 123456 | 690 | COMMIT /* xid=35 */ |
| mysql-bin.000002 | 690 | Rotate | 123456 | 737 | mysql-bin.000003;pos=4 |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
13 rows in set
虽然有很多看似复杂的指令,但是还是不难看出,在02里,有两条写操作,03里有一条删除操作。
一条插入操作的完整日志是这样:
| mysql-bin.000002 | 154 | Anonymous_Gtid | 123456 | 219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 219 | Query | 123456 | 293 | BEGIN |
| mysql-bin.000002 | 293 | Table_map | 123456 | 343 | table_id: 108 (test.table1) |
| mysql-bin.000002 | 343 | Write_rows | 123456 | 390 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000002 | 390 | Xid | 123456 | 421 | COMMIT /* xid=34 */ |
- 我们的目的是恢复误删的数据,其实就是将binlog.000002日志的两条插入记录重演一遍,而不需要取理会binlog.000003的操作(因为删除是一个误操作)
所以现在能理解为什么我们频繁刷新binlog了吧,当然,在实际的线上环境中,我们肯定需要将binlog导出后,仔细筛选出误操作,并将其排除,之后再运行binlog。
在本文中,我们只做一个恢复两条插入语句的操作,执行语句:
sudo mysqlbinlog /var/lib/mysql/mysql-bin.000002 --start-position 154 --stop-position 690 | mysql -uroot -p mytest
注意:这里填写的路径
/var/lib/mysql/mysql-bin.000002需要具体到你的binlog目录,网上大部分文章只写到mysql-bin.000002,如果你不在目录里,mysqlbinlog命令并不会自动定位binlog所在路径。
参数描述:
--start-datetime:从二进制日志中读取指定等于时间戳或者晚于本地计算机的时间
--stop-datetime:从二进制日志中读取指定小于时间戳或者等于本地计算机的时间 取值和上述一样
--start-position:从二进制日志中读取指定position 事件位置作为开始。
--stop-position:从二进制日志中读取指定position 事件位置作为事件截至
执行成功后,再次查看表table1,可以看到两条新的id=3和4的数据被插入了进来。恢复成功了。
mysql> select * from table1;
+----+----+
| id |name|
+----+----+
| 1 | A |
| 2 | B |
| 3 | C |
| 3 | C |
| 4 | D |
+----+----+
6 rows in set
延伸思考
Binlog在什么情况下无法恢复数据?
结语
删库跑路不用怕,其他开发运维都等着恢复你的数据呢,多好的练手机会是不是。
当然,看完binlog日志恢复数据的原理,希望大家以后在定期备份数据库的脚本里,也能够加上刷新binlog日志的命令,这样一旦某天丢失数据,可以将当天binlog数据单独拿出来还原,做到清晰可辨,也加快恢复效率。
参考
https://www.cnblogs.com/rjzheng/p/9721765.html
https://blog.csdn.net/king_kgh/article/details/74890381
https://www.jianshu.com/p/564fcc2b5e31
https://blog.csdn.net/king_kgh/article/details/74833539
关注我
我是一名后端开发工程师。
主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。
各大平台都可以找到我
- 微信公众号:后端技术漫谈
- Github:@qqxx6661
- CSDN:@后端技术漫谈
- 知乎:@后端技术漫谈
- 简书:@后端技术漫谈
- 掘金:@后端技术漫谈
原创博客主要内容
- 后端开发相关技术文章
- Java面试知识点复习全手册
- 设计模式/数据结构
- Leetcode/剑指offer 算法题解析
- SpringBoot/SpringCloud 入门实战系列
- 爬虫相关技术文章
- 逸闻趣事/好书分享/个人兴趣
个人公众号:后端技术漫谈
公众号:后端技术漫谈.jpg
如果文章对你有帮助,不妨收藏,投币,转发,在看起来~
相关推荐
- 1.大白话 Spring_大白话对应的是什么话
-
此文是学习spring源码一些体会,记录下来,哪里有不对的地方,希望批评指正!1.Spring是什么Spring是一款开源的轻量级Java开发框架,旨在提高开发人员的开发效率以及系统的可维护性。...
- MyBatis3.5.11-从入门到高阶_mybatis快速入门
-
一.课程介绍MyBatis概述...
- Spring_spring festival
-
Spring初始化流程,容器初始化,主要流程在AbstractApplicationContext.refresh()1.容器预先准备-...
- 关于Spring的69个面试问答--终极列表
-
这篇文章总结了一些关于Spring框架的重要问题,这些问题都是你在面试或笔试过程中可能会被问到的。下次你再也不用担心你的面试了,JavaCodeGeeks这就帮你解答。大多数你可能被问到的问题都列...
- 49个Spring经典面试题总结(附带答案)
-
点关注,不迷路!课程资料,关注私信【555】获取,还可领取更多Java面试题资料一、一般问题1.不同版本的SpringFramework有哪些主要功能?2.什么是SpringFram...
- MySQL 开发规范_mysql使用规范
-
一、数据库命名规范所有数据对象名称必须小写:db_user禁止使用MySQL保留关键字,若是则引用``...
- Spring框架入门_spring框架入门案例
-
一.spring是什么?Spring是分层...
- JDBC hibernate 和 ibatis 的区别
-
JDBC、Hibernate和MyBatis(前身是iBatis)都是Java中常见的数据库访问技术,它们各有优缺点,适用于不同的场景。下面是它们的一些区别:JDBC是Java连接...
- 万变不离其宗,spring常考知识点总结
-
万变不离其宗,只要理解透了spring常考知识点,那么面试时的问题就好回答了。1.什么是Spring?...
- 大促数据库压力激增,如何一眼定位 SQL 执行来源?
-
作者:京东科技王奕龙你是否曾经遇到过这样的情况:在大促活动期间,用户访问量骤增,数据库的压力陡然加大,导致响应变慢甚至服务中断?更让人头疼的是,当你试图快速定位问题所在时,却发现难以确定究竟是哪个业...
- SSM三大框架整合详细教程_简单介绍一下ssm框架
-
SSM三大框架整合详细教程(SPRING+SPRINGMVC+MYBATIS)使用SSM(Spring、SpringMVC和Mybatis)已经有三个多月了,项目在技术上已经没有什么难...
- MyBatis有哪些核心对象?_mybatis核心接口包括
-
Mybatis基本要素MyBatis有三个基本要素:...
- ibatis 核心原理解析_ibs架构
-
最近查找一个生产问题的原因,需要深入研究ibatis框架的源码。虽然最后证明问题的原因与ibatis无关,但是这个过程加深了对ibatis框架原理的理解。这篇文章主要就来讲讲ibatis...
- python常用得内置函数解析——list()函数
-
ython中最常用的内置函数之一list()。1.函数定义list()函数用于创建一个新的列表对象。...
- 面试干货——某度Python面试题,转发收藏
-
目录1、Python是如何进行内存管理的?2、什么是lambda函数?它有什么好处?...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
如何将AI助手接入微信(打开ai手机助手)
-
Java面试必考问题:什么是乐观锁与悲观锁
-
SparkSQL——DataFrame的创建与使用
-
redission YYDS spring boot redission 使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)