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


2 解決思路
利用MySQL的binlog進(jìn)行數(shù)據(jù)回滾
- 利用binlog文件查詢到修改的那一條記錄
- 對(duì)記錄進(jìn)行反向解析,獲取被修改前數(shù)據(jù)的Update語(yǔ)句
- 執(zhí)行解析后的Update語(yǔ)句,恢復(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 找到誤操作的時(shí)間范圍
這一步僅僅是為了縮小排查區(qū)間
可以通過(guò)對(duì)應(yīng)服務(wù)的日志查詢出大概的誤操作時(shí)間范圍
3.4 登錄MySQL服務(wù)器查找binlog文件
3.4.1 查詢binlog文件路徑
打開MySQL配置文件(通常是/etc/my.cnf或/etc/mysql/my.cnf)
找到與這個(gè)相似的配置(binlog存儲(chǔ)路徑):log-bin=/var/lib/mysql/mysql-bin

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

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

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

3.4.3 確認(rèn)誤操作被存儲(chǔ)在哪一份binlog文件中
我在執(zhí)行誤操作時(shí)大概是7月16日的14:30左右,所以應(yīng)該查看的二進(jìn)制日志文件是binlog.000034
3.5 查看二進(jìn)制日志文件內(nèi)容
3.5.1 利用被更新的表名篩選出大概的時(shí)間點(diǎn)
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 對(duì)每個(gè)時(shí)間點(diǎn)進(jìn)行查詢,找出誤操作的具體時(shí)間和記錄
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解碼:是因?yàn)閎inlog是Base64 編碼的二進(jìn)制數(shù)據(jù),需要解碼
3.5.3 找到誤操作的記錄
更新了多少行數(shù)據(jù),這里就會(huì)有多少個(gè)UPDATE語(yǔ)句

這個(gè)操作記錄中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ù)來(lái)看,哪些字段的數(shù)據(jù)被誤更新了,以及3.5.3的圖片為例
我將全表數(shù)據(jù)的 @4 和 @16都進(jìn)行了錯(cuò)誤更新
所以僅需要以主鍵ID(@1)作為條件,將舊數(shù)據(jù)(WHERE中)的@4 和 @16重新SET回去即可
結(jié)合日志記錄,獲取期望的更新語(yǔ)句樣例為:
UPDATE 數(shù)據(jù)庫(kù).表名 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<>();
// 每個(gè)"### UPDATE "是一條更新語(yǔ)句
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)容,跳過(guò)即可
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語(yǔ)句,實(shí)現(xiàn)回滾
UPDATE hm_product.item_code_distributor_rel SET item_code = 'Ot2djSzc8e',order_channel_id = NULL WHERE id = 1; ..........省略N多條.......
4 最后
這次事情的起因也是因?yàn)橐淮尉帉懘a的粗心造成的,雖然造成的影響不太好,但是解決問(wèn)題的過(guò)程也挺有趣的。
以上就是使用MySQL的Binlog進(jìn)行數(shù)據(jù)回滾的完整流程的詳細(xì)內(nèi)容,更多關(guān)于MySQL Binlog數(shù)據(jù)回滾的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Myeclipse 自動(dòng)生成可持久化類的映射文件的方法
這篇文章主要介紹了Myeclipse 自動(dòng)生成可持久化類的映射文件的方法的相關(guān)資料,這里提供了詳細(xì)的實(shí)現(xiàn)步驟,需要的朋友可以參考下2016-11-11
MySQL為時(shí)間字段設(shè)置默認(rèn)當(dāng)前時(shí)間的方法技巧
文章詳細(xì)介紹了MySQL中記錄創(chuàng)建時(shí)間和最后修改時(shí)間的最佳實(shí)踐,包括時(shí)間類型的支持、默認(rèn)值函數(shù)的使用、MySQL版本的演進(jìn)、常見錯(cuò)誤的修復(fù)以及高級(jí)技巧,建議使用DATETIME或TIMESTAMP類型,并在DEFAULT子句中使用CURRENT_TIMESTAMP函數(shù),需要的朋友可以參考下2026-02-02
Linux系統(tǒng)下自行編譯安裝MySQL及基礎(chǔ)配置全過(guò)程解析
這篇文章主要介紹了Linux系統(tǒng)下自行編譯安裝MySQL及基礎(chǔ)配置全過(guò)程解析,配置方面主要針對(duì)InnoDB引擎來(lái)講,需要的朋友可以參考下2016-02-02
一次MySQL啟動(dòng)導(dǎo)致的事故實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了一次MySQL啟動(dòng)導(dǎo)致的事故實(shí)戰(zhàn)記錄,記錄了MySQL 啟動(dòng)成功但未監(jiān)聽端口的解決方法,文中給出了詳細(xì)的解決方法,需要的朋友可以參考下2021-09-09
MySQL數(shù)據(jù)表常用編碼類型使用及說(shuō)明
文章介紹了MySQL數(shù)據(jù)表中常用的編碼類型,包括ASCII、Latin1、UTF-8、UTF-8mb4和UTF-16,并結(jié)合實(shí)際例子進(jìn)行說(shuō)明,在選擇合適的編碼類型時(shí),需要考慮應(yīng)用的語(yǔ)言范圍、存儲(chǔ)空間、性能和兼容性等因素2026-03-03
MySQL數(shù)據(jù)庫(kù)服務(wù)器逐漸變慢分析與解決方法分享
本文針對(duì)MySQL數(shù)據(jù)庫(kù)服務(wù)器逐漸變慢的問(wèn)題, 進(jìn)行分析,并提出相應(yīng)的解決辦法2012-01-01
mysql從執(zhí)行.sql文件時(shí)處理\n換行的問(wèn)題
后來(lái)注意到,在上面我們恢復(fù)數(shù)據(jù)的時(shí)候是在沒(méi)有連接數(shù)據(jù)的狀態(tài)下執(zhí)行的。2009-05-05
MySQL合并查詢結(jié)果的實(shí)現(xiàn)
本文主要介紹了MySQL合并查詢結(jié)果的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03

