【细如狗】记录一次使用MySQL的Binlog进行数据回滚的完整流程

文章目录
  • [1 事情起因](#1 事情起因)
  • [2 解决思路](#2 解决思路)
  • [3 利用binlog进行数据回滚](#3 利用binlog进行数据回滚)
    • [3.1 确认是否启用Binlog日志](#3.1 确认是否启用Binlog日志)
    • [3.2 确认是否有binlog文件](#3.2 确认是否有binlog文件)
    • [3.3 找到误操作的时间范围](#3.3 找到误操作的时间范围)
    • [3.4 登录MySQL服务器查找binlog文件](#3.4 登录MySQL服务器查找binlog文件)
      • [3.4.1 查询binlog文件路径](#3.4.1 查询binlog文件路径)
      • [3.4.2 找到binlog文件](#3.4.2 找到binlog文件)
      • [3.4.3 确认误操作被存储在哪一份binlog文件中](#3.4.3 确认误操作被存储在哪一份binlog文件中)
    • [3.5 查看二进制日志文件内容](#3.5 查看二进制日志文件内容)
      • [3.5.1 利用被更新的表名筛选出大概的时间点](#3.5.1 利用被更新的表名筛选出大概的时间点)
      • [3.5.2 对每个时间点进行查询,找出误操作的具体时间和记录](#3.5.2 对每个时间点进行查询,找出误操作的具体时间和记录)
      • [3.5.3 找到误操作的记录](#3.5.3 找到误操作的记录)
    • [3.6 保存误操作的记录日志](#3.6 保存误操作的记录日志)
    • [3.7 分析记录,得出需要逆向解析SQL的思路](#3.7 分析记录,得出需要逆向解析SQL的思路)
    • [3.8 编写脚本解析记录,得到SQL](#3.8 编写脚本解析记录,得到SQL)
    • [3.9 执行SQL语句,实现回滚](#3.9 执行SQL语句,实现回滚)
  • [4 最后](#4 最后)

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 最后

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

如果还有别的好方案的话,欢迎在评论区分享。

欢迎大家收藏,但是最好别用到。

感谢大家看到这里,文章如有不足,欢迎大家指出;彦祖点个赞吧彦祖点个赞吧彦祖点个赞吧,欢迎关注程序员五条!

相关推荐
微学AI7 小时前
飞牛 NAS 部署 prompts.chat:自建提示词库、导入社区内容,再配置固定公网访问
数据库·内网穿透
羑悻的小杀马特7 小时前
从写 YAML 到集群自愈:KES-Operator 把 KES 集群接进 Kubernetes 原生体系
运维·数据库·容器·kubernetes
bksczm7 小时前
MySQL基础篇之事务
linux·数据库·sql·mysql
李白客7 小时前
数据库管理工具怎么选?Navicat、DataGrip、DBeaver 六维横向对比与选型建议
运维·数据库
马立杰7 小时前
mysql中Illegal mix of collations for operation “UNION”错误的解决方法
数据库·sql·网络安全
蓝速科技7 小时前
蓝速 K10 三防平板在工业恶劣工况下的选型与应用指南
大数据·运维·数据库·人工智能·科技
bksczm7 小时前
MySQL基础篇之视图与用户管理
linux·数据库·sql·mysql
香菜TTT7 小时前
怎么实现MySQL的主从同步机制
数据库·mysql
邪修king7 小时前
Re:Linux 系统篇(二十九):动静态库Chapter2:动态库深度辨析 —— 核心本质、制作流程、双阶段查找模型与排错指南
android·java·linux·开发语言
Mr YiRan14 小时前
网络请求API监控与网络切换埋点
android·网络