MySql使用binlog还原数据
小编平时操作数据库都很谨慎,特别涉及到数据的增删改的时候,都会反复检查一下sql语句的逻辑性是否准确,但是人有失手马有失蹄,终于在某次操作的时候把表给清了,十几万数据没了,出现这种问题第一反应就应该是赶紧找备份,但是发现没有备份,所以只能寄希望于binlog恢复,也总结了此次binlog使用经验,跟大家分享一下
1. 确认是否符合binlog恢复条件
发现误操作了以后,第一时间是停止进行其他操作,这样方便进行数据的定位
- 确认binlog是开启状态
show variables like '%log_bin%';
- 查看binlog使用哪种模式
- Statement模式,此种格式会记录执行的sql,并不会记录每一行的变化,极大的减少了日志量
- row模式,会记录执行的sql以及每一行的数据变化,如果binlog是这种模式,肯定可以对数据进行恢复
- Mixed模式,是statement和row模式的混合体,由系统判断使用哪种模式
上述三种格式中如果使用的是row模式,恢复起来就比较容易了,通过如下命令查看
show variables like '%binlog_format%';
- 查看当前binlog文件
show master status;
上述命令是用来查看当前使用的binlog,也可以使用如下命令查看所有binlog文件
show binary logs;
2. 恢复数据操作
- 查找要恢复的节点
show binlog events in 'binlog.000003';
上图中标注了几个重要的点要解释一下
- 第一行标注,1355是开始节点,info中是BEGIN
- 第二行标注,test.test1表示是test库中的test1表进行的操作
- 第三行标注,执行的操作类型,此处是写入行操作
- 第四行标注,1564是此操作的结束节点
可以通过这些节点信息的筛选可以大致确定一些节点信息
如果在上述操作中,不能完全准确的找到节点,需要binlog内容解析出来,进行精确查找
mysqlbinlog --no-defaults --base64-output=decode-rows -vv --start-position=1355 --stop-position=3933 --database=test /usr/local/mysql/data/binlog.000003> /root/test1.sql
上述命令简单介绍一下
- –no-defaults用来设置编码的,在win系统中有时候出现编码不一致的情况,此时如果不加此参数就会出现如下报错
```
mysqlbinlog: [ERROR] unknown variable 'default-character-set=utf8'
```
- –base64-output=decode-rows -vv这是两个参数,用来生成可视化日志文件的,如果加上这两个参数,就不要生成sql文件,直接生成txt文件,分析执行的数据以便确定节点
- –start-position=1355 --stop-position=3933 开始节点和结束节点,有时也会使用时间来圈定范围,只需要将节点参数换成时间参数即可–start-datetime=“2023-10-28 05:43:53” --stop-datetime=“2023-10-28 05:50:01”,时间的格式按照上述格式,需要注意的是,如果出现下述错误,需要检查结束节点内是否包含完整的执行日志,如下图结束点为5678才是完整的记录,如果结束点为5521,就会报错,如果在除5678之外的其他节点上,虽然可以生成sql文件,但是执行会有问题
WARNING: The range of printed events ends with a row event or a table map event that does not have the STMT_END_F flag set. This might be because the last statement was not fully written to the log, or because you are using a --stop-position or --stop-datetime that refers to an event in the middle of a statement. The event(s) from the partial statement have not been written to output.
- –database=test 选择要恢复的数据库
- /usr/local/mysql/data/binlog.000003 要使用的binlog文件
- > /root/test1.sql将内容输出到文件
- 直接使用命令恢复
./mysqlbinlog --start-position=开始节点 --stop-position=结束节点 --database=库名 /usr/local/mysql/data/binlog.000003 | mysql -h ip地址 -P 端口号 -uroot -p
- 生成sql文件恢复
- 生成sql文件
./mysqlbinlog --no-defaults --start-position=1355 --stop-position=3933 --database=test /usr/local/mysql/data/binlog.000003> /root/test1.sql
- 进入数据库
#连接数据库
mysql -h ip地址 -P 端口号 -uroot -p
- 执行文件
-- 打开数据库
use 库名称
-- 执行sql文件
source /root/test1.sql
- 生成文本文件,通过提取数据进行恢复
./mysqlbinlog --no-defaults --base64-output=decode-rows -vv --start-position=1355 --stop-position=3933 --database=test /usr/local/mysql/data/binlog.000003> /root/test1.txt
此处不再赘述怎么提取内容进行操作,我进行操作的时候是因为数据是很长时间存储的,已经无法通过语句还原进行操作了,所以使用了提取内容进行解析,反向生成插入语句的方式,下面简单介绍一下如何反向解析
- 首先将误操作的内容提取出来,如下图
此处标出了此条记录操作之前的内容,需要对这个内容进行提取
- 提取内容的方式
我是使用java进行解析的,原理是先判断此块内容属于误操作内容,对里面的字段通过正则提取,然后拼接成(值1,值2)的字符串存入一个集合,这样所有误操作之前的数据就提取出来了,对集合的内容进行拼接成插入语句的形式,其中insert into 表名(字段名1,字段名2)属于固定模板,所以我开始直接每一条数据拼接为一条语句,这样拼接了十几万的插入语句,效率不尽人意,所以更改了策略,每五百条拼接为一条插入语句,这样执行起来效率就特别快了,等有时间我做一个可以解析增删改生成反向语句的小工具,有需要的小伙伴可以@我哦,因为时间原因,将我操作的简易版代码先给小伙伴们参考一下
代码如下
/**
* 此处传入的文件是从binlog里面导出的txt文件,导出语句加了--base64-output=decode-rows -vv的哦
* @param file
* @throws IOException
*/
public static void run(File file) throws IOException {
BufferedReader bf = new BufferedReader(new FileReader(file));
String line = null;
int i = 0;
List<String> list = new ArrayList<>();
for (int j = 0; j < 2193510; j++) {
line = bf.readLine();
if ("### DELETE FROM `test`.`test1`".equals(line)) {
bf.readLine();
Matcher p1 = Pattern.compile("(?<=(@1=)).*(?=( /\\* ))").matcher(bf.readLine());
p1.find();
String value1 = p1.group();
Matcher p2 = Pattern.compile("(?<=(@2=)).*(?=( /\\* ))").matcher(bf.readLine());
p2.find();
String value2 = p2.group();
list.add("(" + value1 + "," + value2 + ")");
i++;
}
}
List<String> sql = new ArrayList<>();
//批量操作每条语句设置插入的条数
int batch = 500;
//起始下标
int start = 0;
while(start < list.size()){
if ((start+batch)<list.size()){
String pinjie = pinjie(list.subList(start, start + batch));
sql.add(pinjie);
} else {
String pinjie = pinjie(list.subList(start,list.size()));
sql.add(pinjie);
}
start += batch;
}
File file1 = new File("D:/insert/file.txt");
file1.createNewFile();
BufferedWriter bw = new BufferedWriter(new FileWriter(file1));
for (String s : sql) {
bw.write(s);
bw.newLine();
}
bw.flush();
bw.close();
bf.close();
System.out.println(i);
}
public static String spliceSql(List<String> list){
String sql = "insert into `test`.`test1`(id,name) values";
for (int i = 0; i < list.size(); i++) {
sql += list.get(i);
if (i < list.size()-1){
sql += ",";
} else {
sql += ";";
}
}
return sql;
}