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

文章目录

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

			// 待拼接的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 最后

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/802817.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PyTorch快速入门教程】02 Jupyter notebook安装及配置

文章目录 1 安装 Jupyter notebook2 安装 ipykernel3 更改 jupyter 默认配置3.1 生成配置文件3.2 关键配置信息 4 扩展插件推荐参考 1 安装 Jupyter notebook 一行命令搞定 python -m pip install jupyter 现在就可以打开Jupyter notebook来运行python啦。 jupyter notebook…

网页设计:HTML标签

一、格式 dreamwave <!DOCTYPE html> <html ><head><meta charset"utf-8"><title>中文测试。。。。</title></head><body>这里是测试body测试内容。。。</body> </html> <head> 网页相关设置 &…

通过vagrant与VirtualBox 创建虚拟机

1.下载vagrant与VirtualBox【windows版本案例】 1.1 vagrant 下载地址 【按需下载】 https://developer.hashicorp.com/vagrant/install?product_intentvagranthttps://developer.hashicorp.com/vagrant/install?product_intentvagrant 1.2 VirtualBox 下载地址 【按需下载…

【漏洞复现】Rejetto HTTP文件服务器——远程命令执行(CVE-2024-23692)

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 Rejetto HTTP文件服务器是一个轻量级的HTTP服务器软件&#xff…

最新vite脚手架配置ts-node工具

在使用最新的vite脚手架进行vue组件开发时&#xff0c;编写的ts文件工具函数的测试&#xff0c;除了应用到组件中进行页面测试&#xff0c;也可以使用ts-node工具进行单独的测试。 在使用最新版本的vite脚手架&#xff0c;生成的tsconfig配置会分出多个配置&#xff1a;tsconf…

【postgresql】时间函数和操作符

日期/时间操作符 加减操作符&#xff1a; 和 - 可以用于日期、时间、时间戳和时间间隔的加减操作。 SELECT 2024-01-01::date INTERVAL 1 day as "date"; ; -- 结果&#xff1a;2024-01-02SELECT 2024-01-01 12:00:00::timestamp - INTERVAL 2 hours as "…

类和对象的简述(c++篇)

开局之前&#xff0c;先来个小插曲&#xff0c;放松一下&#xff1a; 让我们的熊二来消灭所有bug 各位&#xff0c;在这祝我们&#xff1a; 放松过后&#xff0c;开始步入正轨吧。爱学习的铁子们&#xff1a; 目录&#xff1a; 一类的定义&#xff1a; 1.简述&#xff1a; 2…

leetcode简单题26 N.118 杨辉三角 rust描述

// 动态规划 pub fn generate(num_rows: i32) -> Vec<Vec<i32>> {let mut triangle: Vec<Vec<i32>> vec![];for i in 0..num_rows {let mut row vec![1; (i 1) as usize];for j in 1..i as usize {row[j] triangle[(i - 1) as usize][(j - 1)]…

使用阿里云镜像转存的 Harbor 国内镜像源(不定期更新)

目录 Harbor Chart 1.15.0Harbor Chart 1.14.3Harbor Chart 1.13.4 【注】主要用于 helm Charts 安装 Harbor 时自定义镜像地址。 Charts 库&#xff1a;https://artifacthub.io/packages/helm/harbor/harbor Docker 镜像库&#xff1a;https://hub.docker.com/u/goharbor 如果…

十、Java集合 ★ ✔(模块18-20)【泛型、通配符、List、Set、TreeSet、自然排序和比较器排序、Collections、可变参数、Map】

day05 泛型,数据结构,List,Set 今日目标 泛型使用 数据结构 List Set 1 泛型 1.1 泛型的介绍 ★ 泛型是一种类型参数&#xff0c;专门用来保存类型用的 最早接触泛型是在ArrayList&#xff0c;这个E就是所谓的泛型了。使用ArrayList时&#xff0c;只要给E指定某一个类型…

linux的学习(七):读取,函数,正则表达式,文本处理工具cut和awk

##简介 shell编程中的读取&#xff0c;函数&#xff0c;正则表达式&#xff0c;文本处理工具的简单使用 read read&#xff1a;读取控制台的输入 参数&#xff1a; -p&#xff1a;指定读取时的提示符-t&#xff1a;等待读取的时间 脚本例子 编写i.sh脚本&#xff0c;enter…

Android Studio引入ndk编译的so库, 通过jni给Java程序使用

前言 工作要求将一个C老项目的函数用ndk打包成库给安卓同事的java程序调用。 这个任务我debuff拉满&#xff1a; 自己之前从来没接触过安卓开发&#xff0c;问了老板为什么不让安卓开发来干&#xff0c;老板说安卓开发不懂c&#xff0c;公司就我一个是懂c的。。。项目开发年…

自定义短语 - 那些年狠狠惊艳了我们的输入法使用技巧

你是不是每次注册或者登录的时候还在吭哧吭哧地输入你的电话号、邮箱或者昵称&#xff1f; 你是不是在填写各种信息的时候还在一次又一次地输入自己的身份证号、银行卡号或者车牌号&#xff1f; 而我只要输入“mail”&#xff0c;我的候选框里就有6个email号待我挑选&#xf…

css实现前端水印

单处水印 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Watermark Example</title>&l…

单片机设计_自行车码表(AT89C51, LCD1602, DS1302,霍尔传感器)

想要更多项目私wo!!! 一、电路设计 系统采用51单片机LCD1602液晶DS1302时钟模块霍尔传感器电机按键模块蜂鸣器报警模块设计而成。 产品自带单片机上电复位电路、手动复位电路&#xff08;复位按键&#xff09;、晶振电路&#xff08;给单片机提供时钟周期&#xff09;。 …

Java面试题--JVM大厂篇之深入解析JVM中的Serial GC:工作原理与代际区别

目录 引言&#xff1a; 正文&#xff1a; 一、Serial GC工作原理 年轻代垃圾回收&#xff08;Minor GC&#xff09;&#xff1a; 老年代垃圾回收&#xff08;Major GC或Full GC&#xff09;&#xff1a; 二、年轻代和老年代的区别 年轻代&#xff08;Young Generation&a…

数据库基础-进阶

数据库管理&#xff1a; *sql语句 数据库用来增删改查的语句 *** 备份 数据库的数据进行备份 * 主从复制&#xff0c;读写分离&#xff0c;高可用 原理 数据库的概念和相关的语法和规范&#xff1a; 数据库&#xff1a;组织&#xff0c;存储&#xff0c;管理数据的仓库。 数据…

Python用Pyqt5制作音乐播放器

具体效果如下 需要实现的功能主要的几个有&#xff1a; 1、搜索结果更新至当前音乐的列表&#xff0c;这样播放下一首是搜素结果的下一首 2、自动播放 3、滚动音乐文本 4、音乐进度条 5、根据实际情况生成音乐列表。我这里的是下面的情况&#xff0c;音乐文件的格式是 歌…

Calibration相机内参数标定

1.环境依赖 本算法采用张正友相机标定法进行实现&#xff0c;内部对其进行了封装。 环境依赖为 ubuntu20.04 opencv4.2.0 yaml-cpp yaml-cpp安装方式&#xff1a; &#xff08;1&#xff09;git clone https://github.com/jbeder/yaml-cpp.git #将yaml-cpp下载至本地 &a…

Fastjson解析JSON时key对应的value存在多个英文双引号解决

情景 如上图所示&#xff0c;经常在解析json时会出现因双引号报错的问题。 fastjson解决方案 JSONObject jsonfile JSONObject.parseObject(json, Feature.OrderedField);JSONArray jsonArray jsonfile.getJSONObject("result").getJSONArray("items");…