使用MySQL的Binlog进行数据回滚的完整流程

 更新时间:2025年12月03日 09:44:35   作者:e***2829  
本文描述了使用MySQL的binlog进行数据回滚的过程,从确认是否启用Binlog日志到找到误操作的记录,再到编写脚本解析记录,最后执行SQL语句实现回滚,需要的朋友可以参考下

1 事情起因

在最近的一次开发过程中,由于错将eq写成了set,导致全表数据被修改(还好是测试环境)

2 解决思路

利用MySQL的binlog进行数据回滚

  • 利用binlog文件查询到修改的那一条记录
  • 对记录进行反向解析,获取被修改前数据的Update语句
  • 执行解析后的Update语句,恢复数据

3 利用binlog进行数据回滚

3.1 确认是否启用Binlog日志

SHOW VARIABLES LIKE 'log_bin';

3.2 确认是否有binlog文件

SHOW BINARY LOGS;

3.3 找到误操作的时间范围

这一步仅仅是为了缩小排查区间

可以通过对应服务的日志查询出大概的误操作时间范围

3.4 登录MySQL服务器查找binlog文件

3.4.1 查询binlog文件路径

打开MySQL配置文件(通常是/etc/my.cnf或/etc/mysql/my.cnf)

找到与这个相似的配置(binlog存储路径):log-bin=/var/lib/mysql/mysql-bin

如果找不到上述配置,采用另外一种思路获取binlog文件路径,查询日志文件名或索引文件名,能带出binlog的存储路径

    -- 用于查看 MySQL 服务器的二进制日志文件的基本文件名。
    SHOW VARIABLES LIKE 'log_bin_basename';

    -- 用于查看 MySQL 服务器的二进制日志索引文件的名称。
    SHOW VARIABLES LIKE 'log_bin_index'; 

从获取到的结果来看,可以得出binlog是存在于/usr/local/src/mysql/data目录下的

3.4.2 找到binlog文件

3.4.3 确认误操作被存储在哪一份binlog文件中

我在执行误操作时大概是7月16日的14:30左右,所以应该查看的二进制日志文件是binlog.000034

3.5 查看二进制日志文件内容

3.5.1 利用被更新的表名筛选出大概的时间点

mysqlbinlog --no-defaults --start-datetime="2024-07-16 14:00:00" --stop-datetime="2024-07-16 15:00:00" binlog.000034 | grep -i 'item_code_distributor_rel'

3.5.2 对每个时间点进行查询,找出误操作的具体时间和记录

mysqlbinlog --no-defaults --start-datetime="2024-07-16 14:50:27" --stop-datetime="2024-07-16 14:50:28" --base64-output=DECODE-ROWS --verbose binlog.000034 | less

这块需要能够对业务了解,大概知道哪些数据被更新为了什么,被更新了多少条

这样就能够定位到binlog中具体的那条记录了

--base64-output=DECODE-ROWS --verbose 
字段命令的作用是base64解码:是因为binlog是Base64 编码的二进制数据,需要解码

3.5.3 找到误操作的记录

更新了多少行数据,这里就会有多少个UPDATE语句

这个操作记录中SET是被更新的数据,WHERE是原本的数据

3.6 保存误操作的记录日志

mysqlbinlog --no-defaults --start-datetime="2024-07-16 14:50:27" --stop-datetime="2024-07-16 14:50:28" --base64-output=DECODE-ROWS --verbose binlog.000034 > cjh_get_parsed_binlog_2024-07-16-17-05.sql

3.7 分析记录,得出需要逆向解析SQL的思路

需要结合业务来看,哪些字段的数据被误更新了,以及3.5.3的图片为例

我将全表数据的 @4@16都进行了错误更新

所以仅需要以主键ID(@1)作为条件,将旧数据(WHERE中)的@4@16重新SET回去即可

结合日志记录,获取期望的更新语句样例为:

    UPDATE 数据库.表名 SET @4的字段名 = @4,@16的字段名 = @16 WHERE @1的字段名 = @1;

3.8 编写脚本解析记录,得到SQL

package pers.chenjiahao.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ChenJiahao(五条)
 * @date 2024/7/16 17:36
 */
public class MySQLBinaryLogParser {
    public static void main(String[] args) {
        String filePath = "D:工作文件技术文档cjh_get_parsed_binlog_2024-07-16-17-05.sql";
        String document = readFileContent(filePath);
        List<String> updateStatements = parseDocument(document);
        for (String statement : updateStatements) {
            System.out.println(statement);
        }
    }

    /**
     * 读取文本内容
     */
    private static String readFileContent(String filePath) {
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(new File(filePath)))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("
");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content.toString();
    }

    /**
     * 解析文本内容
     */
    private static List<String> parseDocument(String document) {
        List<String> updateStatements = new ArrayList<>();

		// 每个"### UPDATE "是一条更新语句
        String[] sections = document.split("### UPDATE ");
        for (int i = 1; i < sections.length; i++) {
            String section = sections[i];
            String[] lines = section.split("
");

			// 待拼接的WHERE条件
            String whereClause = "";
            // 待拼接的SET
            StringBuilder sb = new StringBuilder();

            for (String line : lines) {
                if (line.startsWith("###   @1=")) {
                    whereClause = "id = " + line.split("=")[1];
                }else if (line.startsWith("###   @4=")) {
                    sb.append("item_code = " + line.split("=")[1]);
                }else if (line.startsWith("###   @16=")) {
                    sb.append(",order_channel_id = " + line.split("=")[1]);
                }

				// 不需要读取日志文件中SET的内容,跳过即可
                if (line.startsWith("### SET")){
                    break;
                }
            }

			// 拼接SQL
            String updateStatement = "UPDATE hm_product.item_code_distributor_rel " + "SET " + sb + " WHERE " + whereClause + ";";

            updateStatements.add(updateStatement);
        }

        return updateStatements;
    }
}

3.9 执行SQL语句,实现回滚

UPDATE hm_product.item_code_distributor_rel SET item_code = 'Ot2djSzc8e',order_channel_id = NULL WHERE id = 1;
..........省略N多条.......

4 最后

这次事情的起因也是因为一次编写代码的粗心造成的,虽然造成的影响不太好,但是解决问题的过程也挺有趣的。

以上就是使用MySQL的Binlog进行数据回滚的完整流程的详细内容,更多关于MySQL Binlog数据回滚的资料请关注脚本之家其它相关文章!

相关文章

  • MySQL的一些常用的SQL语句整理

    MySQL的一些常用的SQL语句整理

    这篇文章主要介绍了MySQL的一些常用的SQL语句整理,非常基础,适合随看随记:)需要的朋友可以参考下
    2015-07-07
  • Windows server 2008 r2上安装MySQL5.7.10步骤

    Windows server 2008 r2上安装MySQL5.7.10步骤

    这篇文章主要介绍了Windows server 2008 r2上安装MySQL5.7.10的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • MYSQL查看时区并设置时区的实现示例

    MYSQL查看时区并设置时区的实现示例

    本文主要介绍了MYSQL查看时区并设置时区的实现示例,包括查看全局和会话时区设置,设置全局和会话时区为东八区,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03
  • 超详细的SQL语句语法汇总

    超详细的SQL语句语法汇总

    个人整理的一些比较常用的SQL语句语法。需要的朋友可以过来参考下
    2013-08-08
  • MySQL分区分表实现方法示例详解

    MySQL分区分表实现方法示例详解

    ShardingSphere-JDBC通过在应用层进行数据分片,可以帮你轻松实现分区、分表和分库,下面我用具体的配置和代码示例来说明如何使用,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-10-10
  • 在MySQL 8.0版本中开启远程登录详细的操作步骤

    在MySQL 8.0版本中开启远程登录详细的操作步骤

    有时数据库所在机器与项目运行的机器不是同一个,那么就涉及到远程链接数据库了,下面这篇文章主要给大家介绍了关于在MySQL 8.0版本中开启远程登录详细的操作步骤,需要的朋友可以参考下
    2024-04-04
  • MySQL派生表合并优化的原理和实现过程

    MySQL派生表合并优化的原理和实现过程

    本文从一个案例出发梳理了MySQL派生表合并优化的流程实现和优化原理,并对优化前后同一条SQL语句在代码层面的类实例映射关系进行了对比,这篇文章主要介绍了MySQL派生表合并优化的原理和实现,需要的朋友可以参考下
    2024-07-07
  • MySQL实现字段的自定义排序的方法

    MySQL实现字段的自定义排序的方法

    一般情况下,我们排序都是直接利用 order by 字段 asc/desc;但是如果要排序的字段数据格式并不能直接实现,或者说我们需要指定的顺序且没有什么规律,简单的order by字段就实现不了,所以本文给大家介绍了MySQL实现字段的自定义排序的方法,需要的朋友可以参考下
    2024-04-04
  • MySQL数据类型之TINYINT类型的使用解析

    MySQL数据类型之TINYINT类型的使用解析

    MySQL 作为最流行的关系型数据库之一,提供了从 TINYINT 到 BIGINT 五种不同范围的整数类型,本文将着重为大家介绍TINYINT类型的使用,需要的小伙伴可以了解下
    2025-05-05
  • mysql 使用profiling和explain查询语句性能解析

    mysql 使用profiling和explain查询语句性能解析

    MySQL 查询 Profile 可以告诉你每个查询花费了多长时间,使用了多少资源,执行了哪些操作等,这篇文章主要介绍了mysql 使用profiling和explain查询语句性能解析,需要的朋友可以参考下
    2024-02-02

最新评论