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


2 解決思路
利用MySQL的binlog進(jìn)行數(shù)據(jù)回滾
- 利用binlog文件查詢到修改的那一條記錄
- 對記錄進(jìn)行反向解析,獲取被修改前數(shù)據(jù)的Update語句
- 執(zhí)行解析后的Update語句,恢復(fù)數(shù)據(jù)
3 利用binlog進(jìn)行數(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)罩疚募蛩饕募軒С鯾inlog的存儲路徑
-- 用于查看 MySQL 服務(wù)器的二進(jìn)制日志文件的基本文件名。
SHOW VARIABLES LIKE 'log_bin_basename';

-- 用于查看 MySQL 服務(wù)器的二進(jìn)制日志索引文件的名稱。
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)該查看的二進(jìn)制日志文件是binlog.000034
3.5 查看二進(jìn)制日志文件內(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 對每個時間點進(jìn)行查詢,找出誤操作的具體時間和記錄
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 編碼的二進(jìn)制數(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都進(jìn)行了錯誤更新
所以僅需要以主鍵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進(jìn)行數(shù)據(jù)回滾的完整流程的詳細(xì)內(nèi)容,更多關(guān)于MySQL Binlog數(shù)據(jù)回滾的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MySQL為時間字段設(shè)置默認(rèn)當(dāng)前時間的方法技巧
文章詳細(xì)介紹了MySQL中記錄創(chuàng)建時間和最后修改時間的最佳實踐,包括時間類型的支持、默認(rèn)值函數(shù)的使用、MySQL版本的演進(jìn)、常見錯誤的修復(fù)以及高級技巧,建議使用DATETIME或TIMESTAMP類型,并在DEFAULT子句中使用CURRENT_TIMESTAMP函數(shù),需要的朋友可以參考下2026-02-02
Linux系統(tǒng)下自行編譯安裝MySQL及基礎(chǔ)配置全過程解析
這篇文章主要介紹了Linux系統(tǒng)下自行編譯安裝MySQL及基礎(chǔ)配置全過程解析,配置方面主要針對InnoDB引擎來講,需要的朋友可以參考下2016-02-02
MySQL數(shù)據(jù)庫服務(wù)器逐漸變慢分析與解決方法分享
本文針對MySQL數(shù)據(jù)庫服務(wù)器逐漸變慢的問題, 進(jìn)行分析,并提出相應(yīng)的解決辦法2012-01-01

