文章目录
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 最后
这次事情的起因也是因为一次编写代码的粗心造成的,虽然造成的影响不太好,但是解决问题的过程也挺有趣的。
如果还有别的好方案的话,欢迎在评论区分享。
欢迎大家收藏,但是最好别用到。
感谢大家看到这里,文章如有不足,欢迎大家指出;彦祖点个赞吧彦祖点个赞吧彦祖点个赞吧,欢迎关注程序员五条!
版权归原作者 五条Programmer 所有, 如有侵权,请联系我们删除。