使用MySQL的Binlog進行數(shù)據(jù)回滾的完整流程
1 事情起因
在最近的一次開發(fā)過程中,由于錯將eq寫成了set,導(dǎo)致全表數(shù)據(jù)被修改(還好是測試環(huán)境)


2 解決思路
利用MySQL的binlog進行數(shù)據(jù)回滾
- 利用binlog文件查詢到修改的那一條記錄
- 對記錄進行反向解析,獲取被修改前數(shù)據(jù)的Update語句
- 執(zhí)行解析后的Update語句,恢復(fù)數(shù)據(jù)
3 利用binlog進行數(shù)據(jù)回滾
3.1 確認(rèn)是否啟用Binlog日志
SHOW VARIABLES LIKE 'log_bin';

3.2 確認(rèn)是否有binlog文件
SHOW BINARY LOGS;

3.3 找到誤操作的時間范圍
這一步僅僅是為了縮小排查區(qū)間
可以通過對應(yīng)服務(wù)的日志查詢出大概的誤操作時間范圍
3.4 登錄MySQL服務(wù)器查找binlog文件
3.4.1 查詢binlog文件路徑
打開MySQL配置文件(通常是/etc/my.cnf或/etc/mysql/my.cnf)
找到與這個相似的配置(binlog存儲路徑):log-bin=/var/lib/mysql/mysql-bin

如果找不到上述配置,采用另外一種思路獲取binlog文件路徑,查詢?nèi)罩疚募蛩饕募?,能帶出binlog的存儲路徑
-- 用于查看 MySQL 服務(wù)器的二進制日志文件的基本文件名。
SHOW VARIABLES LIKE 'log_bin_basename';

-- 用于查看 MySQL 服務(wù)器的二進制日志索引文件的名稱。
SHOW VARIABLES LIKE 'log_bin_index';

從獲取到的結(jié)果來看,可以得出binlog是存在于/usr/local/src/mysql/data目錄下的
3.4.2 找到binlog文件

3.4.3 確認(rèn)誤操作被存儲在哪一份binlog文件中
我在執(zhí)行誤操作時大概是7月16日的14:30左右,所以應(yīng)該查看的二進制日志文件是binlog.000034
3.5 查看二進制日志文件內(nèi)容
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
這塊需要能夠?qū)I(yè)務(wù)了解,大概知道哪些數(shù)據(jù)被更新為了什么,被更新了多少條
這樣就能夠定位到binlog中具體的那條記錄了
--base64-output=DECODE-ROWS --verbose 字段命令的作用是base64解碼:是因為binlog是Base64 編碼的二進制數(shù)據(jù),需要解碼
3.5.3 找到誤操作的記錄
更新了多少行數(shù)據(jù),這里就會有多少個UPDATE語句

這個操作記錄中SET是被更新的數(shù)據(jù),WHERE是原本的數(shù)據(jù)
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的思路
需要結(jié)合業(yè)務(wù)來看,哪些字段的數(shù)據(jù)被誤更新了,以及3.5.3的圖片為例
我將全表數(shù)據(jù)的 @4 和 @16都進行了錯誤更新
所以僅需要以主鍵ID(@1)作為條件,將舊數(shù)據(jù)(WHERE中)的@4 和 @16重新SET回去即可
結(jié)合日志記錄,獲取期望的更新語句樣例為:
UPDATE 數(shù)據(jù)庫.表名 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:工作文件技術(shù)文檔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);
}
}
/**
* 讀取文本內(nèi)容
*/
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();
}
/**
* 解析文本內(nèi)容
*/
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的內(nèi)容,跳過即可
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 執(zhí)行SQL語句,實現(xiàn)回滾
UPDATE hm_product.item_code_distributor_rel SET item_code = 'Ot2djSzc8e',order_channel_id = NULL WHERE id = 1; ..........省略N多條.......
4 最后
這次事情的起因也是因為一次編寫代碼的粗心造成的,雖然造成的影響不太好,但是解決問題的過程也挺有趣的。
以上就是使用MySQL的Binlog進行數(shù)據(jù)回滾的完整流程的詳細(xì)內(nèi)容,更多關(guān)于MySQL Binlog數(shù)據(jù)回滾的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Windows server 2008 r2上安裝MySQL5.7.10步驟
這篇文章主要介紹了Windows server 2008 r2上安裝MySQL5.7.10的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
MYSQL查看時區(qū)并設(shè)置時區(qū)的實現(xiàn)示例
本文主要介紹了MYSQL查看時區(qū)并設(shè)置時區(qū)的實現(xiàn)示例,包括查看全局和會話時區(qū)設(shè)置,設(shè)置全局和會話時區(qū)為東八區(qū),具有一定的參考價值,感興趣的可以了解一下2025-03-03
在MySQL?8.0版本中開啟遠(yuǎn)程登錄詳細(xì)的操作步驟
有時數(shù)據(jù)庫所在機器與項目運行的機器不是同一個,那么就涉及到遠(yuǎn)程鏈接數(shù)據(jù)庫了,下面這篇文章主要給大家介紹了關(guān)于在MySQL?8.0版本中開啟遠(yuǎn)程登錄詳細(xì)的操作步驟,需要的朋友可以參考下2024-04-04
MySQL派生表合并優(yōu)化的原理和實現(xiàn)過程
本文從一個案例出發(fā)梳理了MySQL派生表合并優(yōu)化的流程實現(xiàn)和優(yōu)化原理,并對優(yōu)化前后同一條SQL語句在代碼層面的類實例映射關(guān)系進行了對比,這篇文章主要介紹了MySQL派生表合并優(yōu)化的原理和實現(xiàn),需要的朋友可以參考下2024-07-07
MySQL數(shù)據(jù)類型之TINYINT類型的使用解析
MySQL 作為最流行的關(guān)系型數(shù)據(jù)庫之一,提供了從 TINYINT 到 BIGINT 五種不同范圍的整數(shù)類型,本文將著重為大家介紹TINYINT類型的使用,需要的小伙伴可以了解下2025-05-05
mysql 使用profiling和explain查詢語句性能解析
MySQL 查詢 Profile 可以告訴你每個查詢花費了多長時間,使用了多少資源,執(zhí)行了哪些操作等,這篇文章主要介紹了mysql 使用profiling和explain查詢語句性能解析,需要的朋友可以參考下2024-02-02

