共计 16064 个字符,预计需要花费 41 分钟才能阅读完成。
一、Binlog
1. 简介
MySQL 的二进制日志(Binlog)是一种事务日志,用于记录对数据库的更改操作。
Binlog 主要用于 MySQL 复制和恢复:
- 复制: 从库通过拉取主库的binlog实现主从数据一致
- 恢复: 通过重放binlog恢复数据丢失或误操作情况
2. 原理
在 MySQL 中,每个事务都会在提交后生成相应的 Binlog 记录。MySQL 服务器会为每个客户端连接创建一个线程,称为 Binlog Dump 线程,负责将 Binlog 的内容传送给从服务器,用于数据复制。Binlog 可以在服务器的文件系统中持久化存储,保证了数据的持久性。
3. 格式
MySQL支持多种Binlog格式,包括Statement(语句)、Row(行)和Mixed(混合)三种。不同格式在记录更改时的粒度和复制方式上有所不同:
Row
格式:记录每一行数据的变更,提供了更精确的复制。但产生的日志量较大,因为记录了所有行的变化。(MySQL 8.x 以后的默认格式)Statement
格式:日志文件较小,因为只记录执行的 SQL 语句,简单高效。但不支持某些类型的语句,比如UUID()
、NOW()
等函数,且可能存在非确定性问题,如触发器的执行结果与主服务器不一致。Mixed
格式:结合了Statement和Row格式的优点,MySQL根据具体的情况来选择合适的记录方式。
4. 配置
binlog 的相关配置如下,可以在 my.cnf
配置文件的 [mysqld]
进行配置。
[mysqld]
# 启用日志
log_bin = mysql-bin
# 设置 server_id(在主从复制中必须唯一)
server_id = 1
# 日志格式
binlog_format1 = ROW
# 日志文件最大500M
max_binlog_size = 500M
# 保留7天的日志
#expire_logs_days = 7
# binlog缓存大小
binlog_cache_size = 4m
# 最大binlog缓存大小
max_binlog_cache_size = 512m
# 启用 GTID(全局事务标识符)
gtid_mode = ON
enforce_gtid_consistency = ON
# 设置 binlog 忽略和包括的数据库
binlog_ignore_db = mysql
binlog_do_db = mydatabase
二、数据解析
1. mysqlbinlog
mysqlbinlog
是 MySQL 自带的一个实用工具,用于解析二进制日志文件(binlog)。
- 基本使用:
# binlog-file 为二进制日志文件
mysqlbinlog binlog-file
- 查看详细日志内容:
mysqlbinlog -v binlog-file
- 过滤日志
# 按时间过滤
mysqlbinlog --start-datetime="YYYY-MM-DD HH:MM:SS" --stop-datetime="YYYY-MM-DD HH:MM:SS" binlog-file
# 按位置过滤
mysqlbinlog --start-position=POS --stop-position=POS binlog-file
# 特定数据库
mysqlbinlog --database=mydb binlog-file
常见参数如下:
- -d, --database=name: 仅显示指定数据库的转储内容。
- -o, --offset=#: 跳过前N行的日志条目。
- -r, --result-file=name: 将输入的文本格式的文件转储到指定的文件。
- -s, --short-form: 使用简单格式。
- --set-names=name: 在转储文件的开头增加 'SET NAMES character_set' 语句。
- --start-datetime=name: 转储日志的起始时间。
- --stop-datetime=name: 转储日志的截止时间。
- -j, --start-position=#: 转储日志的起始位置。
- --stop-position=#: 转储日志的截止位置
此外,还有一些其他重要参数:
- --no-defaults: 避免读取默认选项文件,解决配置错误问题。
- -v, --verbose: 显示详细信息,重建行格式并显示为带注释的 SQL 语句。
- -vv: 与 -v 类似,但添加字段数据类型注释。
- --base64-output=DECODE-ROWS: 解码基于行的日志事件,显示为 SQL 语句。
- --skip-gtids: 跳过 GTID 信息的解析。
- --include-gtids: 仅显示指定 GTID 集合中的事务。
- --exclude-gtids: 排除显示指定 GTID 集合中的事务。
2. 日志内容
日志解析出来的基本有如下内容:
- 基本格式
每个事件以 # 开头,后跟事件的描述和时间戳。例如:# at 123456
表示事件在文件中的位置,# 2023-06-21 12:00:00 server id 1
表示事件发生的时间和服务器 ID。
- SQL语句
如果二进制日志格式是基于语句的,即 Statement
格式, mysqlbinlog 将显示实际执行的 SQL 语句。例如:INSERT INTO my_table VALUES (...);
。
- 行事件
如果二进制日志格式是基于行的,mysqlbinlog 将显示行级别的更改,而不是 SQL 语句。使用 --base64-output=DECODE-ROWS
参数可以解码这些行事件,显示为带注释的 SQL 语句。
- 注释
输出中的注释提供了事件的额外信息,如线程 ID、执行时间、错误代码等。
- 事务
事务以 BEGIN 开始,以 COMMIT; 或 ROLLBACK; 结束。
- GTID(全局事务标识符)
在 GTID 格式的日志中,事务前会有 GTID 信息,如 GTID last_committed=1 sequence_number=2 rbr_only=yes。
三、数据恢复案例
1. 数据准备
以下演示一个数据恢复的案例。
docker run -d \
--name mysql8 \
-p 3306:3306 \
-v /home/docker/mysql8/conf.d:/etc/mysql/conf.d \
-v /home/docker/mysql8/data:/var/lib/mysql \
-v /home/docker/mysql8/init:/docker-entrypoint-initdb.d \
-e MYSQL_ROOT_PASSWORD=123456 \
--restart always mysql:8.0.26
此时运行了一个 mysql8,配置文件可以不配置,因为默认使用的是 row
格式。
然后进入容器里面,执行如下命令:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS TestDB;
-- 使用数据库
USE TestDB;
-- 创建表
CREATE TABLE IF NOT EXISTS Users (
UserID INT AUTO_INCREMENT,
UserName VARCHAR(100) NOT NULL,
UserEmail VARCHAR(100) NOT NULL,
PRIMARY KEY (UserID)
);
-- 插入数据
INSERT INTO Users (UserName, UserEmail) VALUES ('Alice', 'alice@example.com');
INSERT INTO Users (UserName, UserEmail) VALUES ('Bob', 'bob@example.com');
INSERT INTO Users (UserName, UserEmail) VALUES ('Charlie', 'charlie@example.com');
-- 更新数据
UPDATE Users SET UserEmail = 'alice_new@example.com' WHERE UserName = 'Alice';
-- 删除数据
DELETE FROM Users WHERE UserName = 'Charlie';
-- 删除表
DROP TABLE IF EXISTS Users;
-- 删除数据库
DROP DATABASE IF EXISTS TestDB;
此时数据库看起来什么都没变,但实际上执行的操作已经被 binlog 文件记录下来。
2. 数据解析
先将日志文件解析出看得懂的文本文件,执行如下命令:
# 刷新日志。主要目的是关闭当前的二进制日志文件并打开一个新的二进制日志文件。
mysql -uroot -p -e "FLUSH LOGS;"
# 使用mysqlbinlog工具来解析Binlog文件,并将其中的SQL语句导出到一个文本文件中
mysqlbinlog -v --base64-output=DECODE-ROWS /var/lib/mysql/mysql-bin.000003 > binlog.sql
这里因为确定所执行的 sql 就在该文件,否则还需要进行查找。
得到的内容如下:
# The proper term is pseudo_replica_mode, but we use this compatibility alias
# to make the statement usable on server versions 8.0.24 and older.
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#240713 8:23:27 server id 1 end_log_pos 125 CRC32 0x0d7adbe1 Start: binlog v 4, server v 8.0.26 created 240713 8:23:27 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
# at 125
#240713 8:23:27 server id 1 end_log_pos 156 CRC32 0x4209701f Previous-GTIDs
# [empty]
# at 156
#240713 8:24:03 server id 1 end_log_pos 233 CRC32 0x7a245fce Anonymous_GTID last_committed=0 sequence_number=1 rbr_only=no original_committed_timestamp=1720859043323243 immediate_commit_timestamp=1720859043323243 transaction_length=205
# original_commit_timestamp=1720859043323243 (2024-07-13 08:24:03.323243 UTC)
# immediate_commit_timestamp=1720859043323243 (2024-07-13 08:24:03.323243 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859043323243*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 233
#240713 8:24:03 server id 1 end_log_pos 361 CRC32 0xa8b3b88d Query thread_id=8 exec_time=0 error_code=0 Xid = 3
SET TIMESTAMP=1720859043/*!*/;
SET @@session.pseudo_thread_id=8/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1168113696/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C latin1 *//*!*/;
SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=255/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
/*!80011 SET @@session.default_collation_for_utf8mb4=255*//*!*/;
/*!80016 SET @@session.default_table_encryption=0*//*!*/;
CREATE DATABASE IF NOT EXISTS TestDB
/*!*/;
# at 361
#240713 8:24:15 server id 1 end_log_pos 440 CRC32 0x75fc9f9e Anonymous_GTID last_committed=1 sequence_number=2 rbr_only=no original_committed_timestamp=1720859058772624 immediate_commit_timestamp=1720859058772624 transaction_length=336
# original_commit_timestamp=1720859058772624 (2024-07-13 08:24:18.772624 UTC)
# immediate_commit_timestamp=1720859058772624 (2024-07-13 08:24:18.772624 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859058772624*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 440
#240713 8:24:15 server id 1 end_log_pos 697 CRC32 0x4881934b Query thread_id=8 exec_time=3 error_code=0 Xid = 8
use `TestDB`/*!*/;
SET TIMESTAMP=1720859055/*!*/;
/*!80013 SET @@session.sql_require_primary_key=0*//*!*/;
CREATE TABLE IF NOT EXISTS Users (
UserID INT AUTO_INCREMENT,
UserName VARCHAR(100) NOT NULL,
UserEmail VARCHAR(100) NOT NULL,
PRIMARY KEY (UserID)
)
/*!*/;
# at 697
#240713 8:24:25 server id 1 end_log_pos 776 CRC32 0xf18611a7 Anonymous_GTID last_committed=2 sequence_number=3 rbr_only=yes original_committed_timestamp=1720859065477045 immediate_commit_timestamp=1720859065477045 transaction_length=317
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1720859065477045 (2024-07-13 08:24:25.477045 UTC)
# immediate_commit_timestamp=1720859065477045 (2024-07-13 08:24:25.477045 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859065477045*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 776
#240713 8:24:25 server id 1 end_log_pos 853 CRC32 0xa770210d Query thread_id=8 exec_time=0 error_code=0
SET TIMESTAMP=1720859065/*!*/;
BEGIN
/*!*/;
# at 853
#240713 8:24:25 server id 1 end_log_pos 917 CRC32 0x904734e2 Table_map: `TestDB`.`Users` mapped to number 89
# at 917
#240713 8:24:25 server id 1 end_log_pos 983 CRC32 0x5c9f4cc8 Write_rows: table id 89 flags: STMT_END_F
### INSERT INTO `TestDB`.`Users`
### SET
### @1=1
### @2='Alice'
### @3='alice@example.com'
# at 983
#240713 8:24:25 server id 1 end_log_pos 1014 CRC32 0xaddd4e19 Xid = 9
COMMIT/*!*/;
# at 1014
#240713 8:24:30 server id 1 end_log_pos 1093 CRC32 0x8ab97df6 Anonymous_GTID last_committed=3 sequence_number=4 rbr_only=yes original_committed_timestamp=1720859070654935 immediate_commit_timestamp=1720859070654935 transaction_length=313
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1720859070654935 (2024-07-13 08:24:30.654935 UTC)
# immediate_commit_timestamp=1720859070654935 (2024-07-13 08:24:30.654935 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859070654935*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 1093
#240713 8:24:30 server id 1 end_log_pos 1170 CRC32 0x642bc228 Query thread_id=8 exec_time=0 error_code=0
SET TIMESTAMP=1720859070/*!*/;
BEGIN
/*!*/;
# at 1170
#240713 8:24:30 server id 1 end_log_pos 1234 CRC32 0x6c6bc11a Table_map: `TestDB`.`Users` mapped to number 89
# at 1234
#240713 8:24:30 server id 1 end_log_pos 1296 CRC32 0xfa7736c8 Write_rows: table id 89 flags: STMT_END_F
### INSERT INTO `TestDB`.`Users`
### SET
### @1=2
### @2='Bob'
### @3='bob@example.com'
# at 1296
#240713 8:24:30 server id 1 end_log_pos 1327 CRC32 0x5b4c0b9e Xid = 10
COMMIT/*!*/;
# at 1327
#240713 8:24:35 server id 1 end_log_pos 1406 CRC32 0x556f8f0a Anonymous_GTID last_committed=4 sequence_number=5 rbr_only=yes original_committed_timestamp=1720859075785273 immediate_commit_timestamp=1720859075785273 transaction_length=321
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1720859075785273 (2024-07-13 08:24:35.785273 UTC)
# immediate_commit_timestamp=1720859075785273 (2024-07-13 08:24:35.785273 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859075785273*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 1406
#240713 8:24:35 server id 1 end_log_pos 1483 CRC32 0xb24fa262 Query thread_id=8 exec_time=0 error_code=0
SET TIMESTAMP=1720859075/*!*/;
BEGIN
/*!*/;
# at 1483
#240713 8:24:35 server id 1 end_log_pos 1547 CRC32 0x86f90af6 Table_map: `TestDB`.`Users` mapped to number 89
# at 1547
#240713 8:24:35 server id 1 end_log_pos 1617 CRC32 0x57177a26 Write_rows: table id 89 flags: STMT_END_F
### INSERT INTO `TestDB`.`Users`
### SET
### @1=3
### @2='Charlie'
### @3='charlie@example.com'
# at 1617
#240713 8:24:35 server id 1 end_log_pos 1648 CRC32 0xd6c2e8c1 Xid = 11
COMMIT/*!*/;
# at 1648
#240713 8:24:47 server id 1 end_log_pos 1727 CRC32 0xee628304 Anonymous_GTID last_committed=5 sequence_number=6 rbr_only=yes original_committed_timestamp=1720859087366034 immediate_commit_timestamp=1720859087366034 transaction_length=362
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1720859087366034 (2024-07-13 08:24:47.366034 UTC)
# immediate_commit_timestamp=1720859087366034 (2024-07-13 08:24:47.366034 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859087366034*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 1727
#240713 8:24:47 server id 1 end_log_pos 1813 CRC32 0x18249c80 Query thread_id=8 exec_time=0 error_code=0
SET TIMESTAMP=1720859087/*!*/;
BEGIN
/*!*/;
# at 1813
#240713 8:24:47 server id 1 end_log_pos 1877 CRC32 0x704bab12 Table_map: `TestDB`.`Users` mapped to number 89
# at 1877
#240713 8:24:47 server id 1 end_log_pos 1979 CRC32 0x9bb1c0b6 Update_rows: table id 89 flags: STMT_END_F
### UPDATE `TestDB`.`Users`
### WHERE
### @1=1
### @2='Alice'
### @3='alice@example.com'
### SET
### @1=1
### @2='Alice'
### @3='alice_new@example.com'
# at 1979
#240713 8:24:47 server id 1 end_log_pos 2010 CRC32 0x3e72c833 Xid = 12
COMMIT/*!*/;
# at 2010
#240713 8:25:03 server id 1 end_log_pos 2089 CRC32 0xc00f3b0c Anonymous_GTID last_committed=6 sequence_number=7 rbr_only=yes original_committed_timestamp=1720859103321244 immediate_commit_timestamp=1720859103321244 transaction_length=321
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1720859103321244 (2024-07-13 08:25:03.321244 UTC)
# immediate_commit_timestamp=1720859103321244 (2024-07-13 08:25:03.321244 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859103321244*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 2089
#240713 8:25:03 server id 1 end_log_pos 2166 CRC32 0xdca5c6e2 Query thread_id=8 exec_time=0 error_code=0
SET TIMESTAMP=1720859103/*!*/;
BEGIN
/*!*/;
# at 2166
#240713 8:25:03 server id 1 end_log_pos 2230 CRC32 0x28c1cab3 Table_map: `TestDB`.`Users` mapped to number 89
# at 2230
#240713 8:25:03 server id 1 end_log_pos 2300 CRC32 0x204184e7 Delete_rows: table id 89 flags: STMT_END_F
### DELETE FROM `TestDB`.`Users`
### WHERE
### @1=3
### @2='Charlie'
### @3='charlie@example.com'
# at 2300
#240713 8:25:03 server id 1 end_log_pos 2331 CRC32 0x098f962d Xid = 13
COMMIT/*!*/;
# at 2331
#240713 8:25:12 server id 1 end_log_pos 2408 CRC32 0x8f190e29 Anonymous_GTID last_committed=7 sequence_number=8 rbr_only=no original_committed_timestamp=1720859112568509 immediate_commit_timestamp=1720859112568509 transaction_length=221
# original_commit_timestamp=1720859112568509 (2024-07-13 08:25:12.568509 UTC)
# immediate_commit_timestamp=1720859112568509 (2024-07-13 08:25:12.568509 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859112568509*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 2408
#240713 8:25:12 server id 1 end_log_pos 2552 CRC32 0xdd982cad Query thread_id=8 exec_time=0 error_code=0 Xid = 14
SET TIMESTAMP=1720859112/*!*/;
DROP TABLE IF EXISTS `Users` /* generated by server */
/*!*/;
# at 2552
#240713 8:25:18 server id 1 end_log_pos 2629 CRC32 0x763c59b4 Anonymous_GTID last_committed=8 sequence_number=9 rbr_only=no original_committed_timestamp=1720859118586212 immediate_commit_timestamp=1720859118586212 transaction_length=197
# original_commit_timestamp=1720859118586212 (2024-07-13 08:25:18.586212 UTC)
# immediate_commit_timestamp=1720859118586212 (2024-07-13 08:25:18.586212 UTC)
/*!80001 SET @@session.original_commit_timestamp=1720859118586212*//*!*/;
/*!80014 SET @@session.original_server_version=80026*//*!*/;
/*!80014 SET @@session.immediate_server_version=80026*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 2629
#240713 8:25:18 server id 1 end_log_pos 2749 CRC32 0xdea6f9b6 Query thread_id=8 exec_time=0 error_code=0 Xid = 15
SET TIMESTAMP=1720859118/*!*/;
DROP DATABASE IF EXISTS TestDB
/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
可以看见,这里记录了之前对数据库进行的操作。比如现在想恢复 DELETE
之前的记录,那么应该找到如下命令:
DELETE FROM Users WHERE UserName = 'Charlie';
找到相关记录如下:
BEGIN
/*!*/;
# at 2166
#240713 8:25:03 server id 1 end_log_pos 2230 CRC32 0x28c1cab3 Table_map: `TestDB`.`Users` mapped to number 89
# at 2230
#240713 8:25:03 server id 1 end_log_pos 2300 CRC32 0x204184e7 Delete_rows: table id 89 flags: STMT_END_F
### DELETE FROM `TestDB`.`Users`
### WHERE
### @1=3
### @2='Charlie'
### @3='charlie@example.com'
# at 2300
#240713 8:25:03 server id 1 end_log_pos 2331 CRC32 0x098f962d Xid = 13
COMMIT/*!*/;
3. 数据恢复
此时可以导出需要的内容:
mysqlbinlog --stop-position=2166 /var/lib/mysql/mysql-bin.000003 > binlog.sql
这里是因为确定这个文件里面的内容都是需要的,否则可能还需要指定 --start-position
这里面的内容就是没删除之前的数据了,直接进行恢复即可。
mysql -u root -p < binlog.sql
此时数据就已经恢复了。
当然,如果数据量更多,更复杂,需要根据实际情况进行操作,这里只是一个简单的演示。
需要补充的一点是,注意到 binlog 解析出来的文件:
/** 更新 */
BEGIN
/*!*/;
# at 1813
#240713 8:24:47 server id 1 end_log_pos 1877 CRC32 0x704bab12 Table_map: `TestDB`.`Users` mapped to number 89
# at 1877
#240713 8:24:47 server id 1 end_log_pos 1979 CRC32 0x9bb1c0b6 Update_rows: table id 89 flags: STMT_END_F
### UPDATE `TestDB`.`Users`
### WHERE
### @1=1
### @2='Alice'
### @3='alice@example.com'
### SET
### @1=1
### @2='Alice'
### @3='alice_new@example.com'
# at 1979
#240713 8:24:47 server id 1 end_log_pos 2010 CRC32 0x3e72c833 Xid = 12
COMMIT/*!*/;
/** 删除 */
BEGIN
/*!*/;
# at 2166
#240713 8:25:03 server id 1 end_log_pos 2230 CRC32 0x28c1cab3 Table_map: `TestDB`.`Users` mapped to number 89
# at 2230
#240713 8:25:03 server id 1 end_log_pos 2300 CRC32 0x204184e7 Delete_rows: table id 89 flags: STMT_END_F
### DELETE FROM `TestDB`.`Users`
### WHERE
### @1=3
### @2='Charlie'
### @3='charlie@example.com'
# at 2300
#240713 8:25:03 server id 1 end_log_pos 2331 CRC32 0x098f962d Xid = 13
COMMIT/*!*/;
这里不管是更新或者是删除,都将原来的字段,并且是所有的值都给列举了出来。而我们原来的更新和删除语句仅仅是根据条件进行的。
如果此时数据库和表没有删除,仅仅是误执行这两个操作,那么可以根据上面的日志进行如下修复:
# 恢复错误的删除
INSERT INTO Users (UserID, UserName, UserEmail) VALUES (3, 'Charlie', 'charlie@example.com');
# 恢复错误的更新
UPDATE Users SET UserName = 'Alice', UserEmail = 'alice@example.com' WHERE UserID = 1;
4. 补充
如果数据都是删除,那么可以利用如下命令将 delete 语句转换成 insert 语句。但是需要注意替换两处,第一个是数据库字段,第二个是表字段实际数量。
cat binlog.sql | \
sed -n '/###/p' | \
sed 's/### //g;s/\/\*.*/,/g;s/DELETE FROM/INSERT INTO/g;s/WHERE/(UserID, UserName, UserEmail) VALUES /g;' | \
sed 's/@1=/(/;s/@3=\(.*\)/,\1)\;/' | \
sed 's/@[1-9][0-9]*=/,/g' > restore.sql
修改 (UserID, UserName, UserEmail)
为表的实际字段,修改第 4 行 的 @3
为表字段的实际数量。