# T0 select获取检测单的异常记录 select id, exception_type, qc_order_id from qc_order_exception where qc_order_id in (1001, 1002) # T1 执行如下SQL,ID为1001, 1002更新检测单记录的状态。 注: 更新前 status = 10 update qc_order set status = 20 where id in (1001, 1002); # T2 执行如下SQL,通过ID获取检测单信息。 注: T1更新为satus=20的记录,可能读取出来的 status = 10 select id, status, from qc_order where id in (1001, 1002) # T3 根据检测单状态,执行不同的业务逻辑。 if (qcOrder.getStatus() == 20) { // 检测单无异常 log.info("逻辑1"); // 正常情况:1001、1002都执行这里逻辑。打印日志: "逻辑1" } else { log.info("逻辑2"); // 偶发异常现象:1002走上面逻辑 1001走了这里的逻辑。 打印日志: "逻辑2"。但分明T1时刻ID = 1001这条记录的status应该已经被更新为了20 }
接到业务反馈后,我们在 打印日志: "逻辑2" 的分支中,加了监控告警,触发告警后,检测单没有后续的操作。这时去查看数据库中检测单的状态,没有异常的检测单的状态均为 status = 20(检测单最后状态是对的,但是在处理业务逻辑时,可能读取出旧值 status=10)。
undrop-for-innodb 是一款专为 InnoDB 存储引擎设计的开源数据恢复工具,支持从文件级别恢复误删除、损坏或丢失的数据。也可以用来从ibd文件中解析出表的数据,其兼容 MySQL 5.7 版本。
执行如下步骤:
# 1. 关闭 MySQL,复制 qc_order的ibd文件到 ./data 目录。需要确保MySQL参数, innodb_file_per_table=on [root@localhost undrop-for-innodb]# ls -lh ./data/ total 96K -rw-r-----. 1 root root 96K Aug 17 21:49 qc_order.ibd # 2. 使用 stream_parser 进行拆页 [root@localhost undrop-for-innodb]# ./undrop-for-innodb/stream_parser -f ./data/qc_order.ibd Opening file: ./data/qc_order.ibd
File information: ID of device containing file: 64768 inode number: 70389401 protection: 100640 (regular file) number of hard links: 1 user ID of owner: 0 group ID of owner: 0 device ID (if special file): 0 blocksize for filesystem I/O: 4096 number of blocks allocated: 192 time of last access: 1755438550 Sun Aug 17 21:49:10 2025 time of last modification: 1755438550 Sun Aug 17 21:49:10 2025 time of last status change: 1755438825 Sun Aug 17 21:53:45 2025 total size, in bytes: 98304 (96.000 kiB) Size to process: 98304 (96.000 kiB) All workers finished in 0 sec # 会生成目录:pages-qc_order.ibd/,其中 FIL_PAGE_INDEX/ 里是索引页 # 3. 确定叶子页文件名 [root@localhost undrop-for-innodb]# ls -lh ./pages-qc_order.ibd/FIL_PAGE_INDEX/ total 16K -rw-r--r--. 1 root root 16K Aug 17 21:57 0000000000000049.page # 4. 准备qc_order的DDL。 # undrop-for-innodb 的 c_parser 在解析 CREATE TABLE 时有个限制 —— 它的 SQL parser 只接受 简化版的 DDL,不能有多余的引号或不支持的选项。 [root@localhost undrop-for-innodb]# cat ./qc_order.sql CREATE TABLE qc_order ( id INT NOT NULL, status INT NOT NULL, create_time DATETIME NOT NULL, update_time DATETIME NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=Dynamic; # 5. 用 c_parser 解析叶子页。数据输出到 dump.tsv [root@localhost undrop-for-innodb]# ./undrop-for-innodb/c_parser -6f ./pages-qc_order.ibd/FIL_PAGE_INDEX/0000000000000049.page -t ./qc_order.sql > ./dump.tsv SET FOREIGN_KEY_CHECKS=0; LOAD DATA LOCAL INFILE '/root/undrop-for-innodb/dumps/default/qc_order' REPLACE INTO TABLE `qc_order` CHARACTER SET UTF8 FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'qc_order\t' (`id`, `status`, `create_time`, `update_time`); -- STATUS {"records_expected": 2, "records_dumped": 2, "records_lost": false} STATUS END # 6. 查看 dump.tsv 内容 [root@localhost undrop-for-innodb]# cat dump.tsv -- Page id: 3, Format: COMPACT, Records list: Valid, Expected records: (2 2) 00000000110C 2A0000013C0323 qc_order 1001 20 "2025-08-17 17:04:35.0" "2025-08-17 17:06:57.0" 00000000110E 2B0000013D0403 qc_order 1002 20 "2025-08-17 17:04:35.0" "2025-08-17 17:07:42.0" -- Page id: 3, Found records: 2, Lost records: NO, Leaf page: YES # 第一列为记录的 DB_TRX_ID,以十六进制输出,也就是我们需要找的数据了。转换为 10进制: 00000000110C -> 4364 (id=1001) 00000000110E -> 4366 (id=1002)