威马汽车时隔一年估值爆涨110亿,背后来源于这些大事件。

「删库跑路」使用Binlog日志恢复误删的MySQL数据

前言

“删库跑路”是程序员经常谈起的话题,今天,我就要教大家如何删!库!跑!路!

「删库跑路」使用Binlog日志恢复误删的MySQL数据

开个玩笑,今天文章主题是如何使用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>showvariableslike\'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|
+---------------------------------+-------+
5rowsinset(0.00sec)

可以看到我们的binlog是关闭的,都是OFF。接下来我们需要修改Mysql配置文件,执行命令:

sudovi/etc/mysql/mysql.conf.d/mysqld.cnf

在文件末尾添加:

log-bin=/var/lib/mysql/mysql-bin

保存文件,重启mysql服务:

sudoservicemysqlrestart

重启完成后,查看下mysql的状态:

systemctlstatusmysql.service

这时,如果你的mysql版本在5.7或更高版本,就会报错:

Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.190791Z0[Warning]Changedlimits:max_open_files:1024(requested5000)
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.190839Z0[Warning]Changedlimits:table_open_cache:431(requested2000)
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.359713Z0[Warning]TIMESTAMPwithimplicitDEFAULTvalueisdeprecated.Pleaseuse--explicit_defaults_for_timestampserveroption(se
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.361395Z0[Note]/usr/sbin/mysqld(mysqld5.7.28-0ubuntu0.16.04.2-log)startingasprocess5930...
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.363017Z0[ERROR]Youhaveenabledthebinarylog,butyouhaven\'tprovidedthemandatoryserver-id.Pleaserefertotheproperserver
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.363747Z0[ERROR]Aborting
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.363922Z0[Note]Binlogend
Jan0615:49:58VM-0-11-ubuntumysqld[5930]:2020-01-06T07:49:58.364108Z0[Note]/usr/sbin/mysqld:Shutdowncomplete
Jan0615:49:58VM-0-11-ubuntusystemd[1]:mysql.service:Mainprocessexited,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

模拟删除数据并恢复

  1. 首先新建数据库mytest,新建一张表table1,结构见下方SQL代码
CREATEDATABASE`test`;

USE`test`;

DROPTABLEIFEXISTS`table1`;

CREATETABLE`table2`(
`id`int(11)DEFAULTNULL,
`name`varchar(20)DEFAULTNULL
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;
  1. 插入两条数据,分别是 (1,\'A\'),(2,\'B\')
INSERTINTO`table1`VALUES(1,\'A\'),(2,\'B\');
  1. 我们看一下binlog日志的状态,使用show master status
mysql>showmasterstatus
->;
+------------------+----------+--------------+------------------+-------------------+
|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
+------------------+----------+--------------+------------------+-------------------+
|mysql-bin.000001|690||||
+------------------+----------+--------------+------------------+-------------------+
1rowinset

binlog日志特征:每当我们重启MySQL一次,会自动生成一个binlog文件,当然,我们也可以手动的来刷新binlog文件,通过 flush logs,同样会新创建一个binlog文件。实际上当服务器在重启时,也会调用flush logs操作。

上图代码中可以看到,现在我们正在使用 mysql-bin.0000001 ,并且这个文件现在正在记录到690行。

  1. 然后,使用flush logs来主动刷新一次binlog
mysql>flushlogs;
QueryOK,0rowsaffected

mysql>showmasterstatus
->;
+------------------+----------+--------------+------------------+-------------------+
|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
+------------------+----------+--------------+------------------+-------------------+
|mysql-bin.000002|154||||
+------------------+----------+--------------+------------------+-------------------+
1rowinset

可以看到,现在日志文件在 mysql-bin.000002 文件中,位置为154。也就是我们主动刷新了一次binlog,生成了新的000002,而000001则已经归档了,不会再写入新的日志进去了。

  1. 接下来我们在插入两条数据
insertintotable1values(3,\'C\');
insertintotable1values(4,\'D\');
mysql>select*fromtable1;
+----+----+
|id|name|
+----+----+
|1|A|
|2|B|
|3|C|
|4|D|
+----+----+
  1. 这时候我们已经有了四条数据,我们再次flush logs,把mysql-bin.000002日志存档,开启新的mysql-bin.000003日志,这样,每次我们插入的数据彼此独立。实际情况下,binlog会比较复杂,这里也是做了简化,为了理解更方便。
mysql>flushlogs;
QueryOK,0rowsaffected

mysql>showmasterstatus;
+------------------+----------+--------------+------------------+-------------------+
|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
+------------------+----------+--------------+------------------+-------------------+
|mysql-bin.000003|154||||
+------------------+----------+--------------+------------------+-------------------+
1rowinset
  1. 然后我们删除id为4的数据(4,D),并且再次刷新binlog,如此一来,binlog.000003里面只有一条删除操作。
mysql>deletefromtable1whereid=4;
QueryOK,1rowaffected

mysql>showmasterstatus;
+------------------+----------+--------------+------------------+-------------------+
|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
+------------------+----------+--------------+------------------+-------------------+
|mysql-bin.000003|423||||
+------------------+----------+--------------+------------------+-------------------+
1rowinset

mysql>flushlogs;
QueryOK,0rowsaffected

mysql>showmasterstatus;
+------------------+----------+--------------+------------------+-------------------+
|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|
+------------------+----------+--------------+------------------+-------------------+
|mysql-bin.000004|154||||
+------------------+----------+--------------+------------------+-------------------+
1rowinset
  1. 让我们来好好观察下mysql-bin.00002和mysql-bin00003两个binlog,使用命令:show binlog events in \'mysql-bin.000003\'
mysql>showbinlogeventsin\'mysql-bin.000003\';
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
|Log_name|Pos|Event_type|Server_id|End_log_pos|Info|
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
|mysql-bin.000003|4|Format_desc|123456|123|Serverver:5.7.28-0ubuntu0.16.04.2-log,Binlogver: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:108flags:STMT_END_F|
|mysql-bin.000003|392|Xid|123456|423|COMMIT/*xid=39*/|
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
7rowsinset

mysql>showbinlogeventsin\'mysql-bin.000002\';
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
|Log_name|Pos|Event_type|Server_id|End_log_pos|Info|
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
|mysql-bin.000002|4|Format_desc|123456|123|Serverver:5.7.28-0ubuntu0.16.04.2-log,Binlogver: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:108flags: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:108flags: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|
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
13rowsinset

虽然有很多看似复杂的指令,但是还是不难看出,在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:108flags:STMT_END_F|
|mysql-bin.000002|390|Xid|123456|421|COMMIT/*xid=34*/|
  1. 我们的目的是恢复误删的数据,其实就是将binlog.000002日志的两条插入记录重演一遍,而不需要取理会binlog.000003的操作(因为删除是一个误操作)

所以现在能理解为什么我们频繁刷新binlog了吧,当然,在实际的线上环境中,我们肯定需要将binlog导出后,仔细筛选出误操作,并将其排除,之后再运行binlog。

在本文中,我们只做一个恢复两条插入语句的操作,执行语句:

sudomysqlbinlog/var/lib/mysql/mysql-bin.000002--start-position154--stop-position690|mysql-uroot-pmytest

注意:这里填写的路径/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*fromtable1;
+----+----+
|id|name|
+----+----+
|1|A|
|2|B|
|3|C|
|3|C|
|4|D|
+----+----+
6rowsinset

延伸思考

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 入门实战系列
  • 爬虫相关技术文章
  • 逸闻趣事/好书分享/个人兴趣

个人公众号:后端技术漫谈

「删库跑路」使用Binlog日志恢复误删的MySQL数据

公众号:后端技术漫谈.jpg

如果文章对你有帮助,不妨收藏,投币,转发,在看起来~

上一篇

华为的故事告诉我们如何让一个三流企业发展成一流企业

下一篇

快播为什么快,都是因为P2P

评论已经被关闭。

插入图片
返回顶部