搞数据库运维的,几乎都遇到过这种揪心场景:业务反馈某张表里几百条记录没了,查了半天发现是凌晨一个误删除操作,偏偏备份是凌晨2点的,恢复完还得补一整天数据。金仓数据库KingbaseES提供的WalMiner工具,就是专治这种“数据丢得不明不白”的利器。它能把数据库写盘前那本“流水账”——也就是WAL日志,翻译成肉眼可读的SQL语句,让你在不停库、不还原整库的前提下,把某个表、某个事务在某个时间段内做过的增删改一条条翻出来,甚至能直接生成undo语句做定向恢复。
这篇文章我会按“原理—配置—实操—排查”这条线,把WalMiner从装到用完整过一遍。适合负责数据运维的DBA、经常写数据处理任务的应用开发,以及做数据治理和审计的同学参考。
1. WalMiner为什么是运维刚需:先搞懂WAL日志
1.1 WAL日志等于数据库的“黑匣子”
KingbaseES沿用了PostgreSQL系的预写式日志机制,也就是WAL(Write-Ahead Logging)。所有数据页的修改不会立刻落盘,而是先把“我要改哪张表的哪个块、改成什么样子”描述成一条日志记录,追加写到WAL文件里;只有WAL记录真正落盘后,事务才算提交成功。也就是说,磁盘上的WAL文件永远比数据文件“知道得更多一点”,它是数据库发生过的所有操作的忠实流水账。
这套机制的引入最初是为了崩溃恢复:数据库中途挂了,重启时通过WAL把没有完全写入数据文件的修改重新应用一遍,就能保证数据完整。但从运维视角来看,WAL还有一个隐藏价值:它相当于飞机上的黑匣子,记录了事务ID、操作类型、行级变更前后镜像等信息。只要有WAL文件,理论上就能回放出任意时间点数据库做过什么。
WalMiner做的事情,就是把这个“黑匣子”里的二进制内容翻译成人能看懂的表名、字段、SQL语句。它并不需要你在线上库做任何数据拷贝,只是去读WAL文件并解析,因此对业务几乎零侵入。这一点在事故场景下特别重要,很多时候我们根本没有时间去停库、去搭一个完整的环境做还原。
1.2 有了备份和归档,为什么还要WalMiner
很多团队都有完整备份加WAL归档,可以做到时间点恢复(PITR)。但PITR有个明显痛点:它是实例级别的。比如某张订单表在上午10点被误删了一批数据,你选择PITR恢复到10点之前,那这个实例里其他所有表从10点到现在的数据变化也全丢了。恢复之后要做数据回填、补录、对账,工作量非常大,而且业务中断时间不可控。
WalMiner的定位和PITR完全不同,它解决的是“单表、单事务、单条SQL”级别的精准定位和恢复。它可以告诉你某个事务在某个时刻DELETE了哪些行,并且给出这些行的原始值,也就是undo语句。你可以只把这一批数据补回去,不影响其他任何业务,恢复粒度精确到行。
我把两者放在一起对比一下:
| 恢复方式 | 恢复粒度 | 是否需要停库 | 对业务影响 | 适用场景 |
|---|---|---|---|---|
| 全量备份+PITR | 整个实例 | 通常需要 | 大,涉及全库回档 | 库级损坏、灾难恢复 |
| 逻辑备份+应用Binlog/日志 | 部分库表 | 视工具而定 | 中 | 数据同步、归档分析 |
| WalMiner日志解析 | 单表、单事务、单行 | 不需要 | 小,只补受影响数据 | 误操作、定向审计、数据找回 |
这个对比并不是说PITR没用,而是说在日常运维中,绝大多数“找数据”的诉求其实只是某张表或者某一个事务的问题。用PITR相当于为了修一块玻璃把整栋楼推倒重建,而WalMiner是让你拿着图纸精准替换那块玻璃。
2. 环境准备:把WalMiner用起来之前要做的三件事
2.1 先把wal_level调整到logical
WalMiner解析WAL并还原出SQL语句,前提是WAL日志里记录了足够多的行级信息。KingbaseES的wal_level参数有三个常见级别:minimal、replica、logical。默认通常是replica,但要注意,如果只设置为replica,有一部分新增语句在日志里不会保留完整的行镜像,WalMiner解析时可能拿不到关键信息。为了让日志解析尽量完整,建议提前将实例设为logical级别。
ALTER SYSTEM SET wal_level = logical; ALTER SYSTEM SET max_wal_senders = 20; ALTER SYSTEM SET max_replication_slots = 20;这里解释一下为什么我看重max_wal_senders和max_replication_slots。开启logical级别后,逻辑复制槽和WAL发送进程都可能被用到,如果连接数配置不足,日志保留和解析都可能受影响。数值可以根据实例规格放大,但一般情况下这两个参数同时调到20左右已经够用。
修改完wal_level后必须重启数据库实例才能生效,因为是静态参数,不能热加载:
-- 重启前先确认集群状态正常 SELECT version(); -- 重启后在会话里确认 SHOW wal_level;如果查询结果还是replica,说明没有真正重启成功或者修改被覆盖了,需要检查配置文件里是否有重复设置。
2.2 开启归档模式并保证WAL不会被提前清理
解析日志最怕什么?最怕需要回溯的WAL文件已经被覆盖或清理掉了。KingbaseES的WAL文件默认是循环使用的,达到max_wal_size以后,旧文件会被回收复用。所以务必开启归档模式,把历史WAL文件保存到独立目录。
ALTER SYSTEM SET archive_mode = on; ALTER SYSTEM SET archive_command = 'cp %p /kbbackup/archive/%f';归档目录需要提前创建好,并且要确保数据库进程有写权限。%p是源WAL文件路径,%f是文件名,格式是固定的,不要随意改。我习惯把归档目录放在单独的存储上,避免和数据库数据文件抢磁盘IO。
还有一个容易被忽视的参数:在线WAL的保留量。KingbaseES不同版本参数名不同,常见的是wal_keep_size(较新版本)或wal_keep_segments(较老版本)。我一般会额外设置一小段在线日志保留:
ALTER SYSTEM SET wal_keep_size = '2GB';含义是在当前数据目录下保留最近2GB的在线WAL,即使归档已经拷贝走,也不会被立即回收。这样即使归档命令偶尔失败,还有一段缓冲时间排查。需要保留多大,可以根据业务变更量来评估:单段WAL通常是16MB,如果高峰期每小时产生约1GB日志,那么至少保留够一两个小时的回溯窗口。
2.3 安装并加载walminer扩展
KingbaseES的发行包中通常自带WalMiner相关文件,但需要手动创建扩展。用管理员账号执行:
CREATE EXTENSION walminer;如果报错找不到扩展,先进去看看扩展文件是否部署完整:
-- 检查扩展目录 ls $KINGBASE_HOME/share/extension/walminer*如果文件确实缺失,可以使用安装镜像里的walminer安装包,或者联系技术支持获取对应数据库版本的WalMiner插件包。有一点必须提醒:WalMiner的插件版本和数据库内核版本强相关,版本不匹配轻则解析出乱码,重则扩展加载失败。安装前一定要确认两者对应。
加载扩展后,可以查看当前实例的归档设置以及WAL目录位置,为后续解析做准备:
SELECT setting FROM sys_settings WHERE name = 'archive_mode'; SHOW data_directory;3. 核心实操:日志解析与数据找回全流程
3.1 启动和停止WalMiner进程
WalMiner在运行时需要有一个会话作为“分析进程”,通过函数来控制启停。启动时建议给分析任务起一个容易识别的名称,比如用日期和用途拼接:
-- 启动分析进程 SELECT walminer_start('walminer_demo_20240618');启动成功后,后续解析操作都在当前会话上下文里进行。如果任务做完,要记得停止,否则会占用一点系统资源:
SELECT walminer_stop();有一点和直觉不同:WalMiner停止后,已经解析出来的结果集不会丢,它已经被写入到walminer_logs视图对应的临时表里了。你可以继续查询、导出。只有再次启动并解析新的WAL范围时,旧的结果集才会被清理。
3.2 解析WAL文件:三种常见姿势
WalMiner提供了解析WAL文件的函数接口,最常见的用法有三种。
第一种,按单个WAL文件解析。适用于你已经定位到某个时间段的日志文件,比如误操作发生在0000000100000016000000B4这个段里:
SELECT walminer_read_walfile('/kbbackup/archive/0000000100000016000000B4');第二种,按目录解析。如果事故时间点跨度较长,或者不确定具体是哪个文件,可以直接给归档目录,让它批量扫描:
SELECT walminer_read_walfile('/kbbackup/archive/');第三种,按LSN范围解析。这是最精准的方式,LSN是WAL日志内部的逻辑位置,每条变更都有唯一的LSN。你可以在误操作前后记录两个LSN点:
SELECT walminer_read_walfile(1, '16/B3F00000'::pg_lsn, '16/B4F00000'::pg_lsn);第一个参数1是时间线(Timeline),后面两个参数分别是起始LSN和结束LSN。相比按文件解析,按LSN解析的好处是结果范围干净、不会混入无关事务,而且解析耗时更短。我个人的习惯是优先用LSN方式,除非现场实在太紧急来不及查LSN,才退回去按目录全量扫。
解析是一个相对耗时的过程,如果归档目录里文件很多,建议先确认时间范围,再决定用哪种方式,避免无谓地全量扫描。
3.3 实战案例:把误删的订单表数据找回来
场景设定:某业务库中有一张订单表public.t_order,在上午10点15分左右被一条DELETE FROM public.t_order WHERE status = 0;误删了约300行,业务方在10点40分才发现问题。现在需要找回这300行数据。
第一步是定位WAL文件。我们可以通过误删时间去推算,也可以在故障现场尝试获取当前WAL文件名,然后按时间往前找:
SELECT pg_walfile_name(pg_current_wal_lsn());如果归档目录有备份,按修改时间排序就能锁定10点15分前后的WAL段。假设锁定到两个文件,那就依次解析:
SELECT walminer_read_walfile('/kbbackup/archive/0000000100000016000000B4'); SELECT walminer_read_walfile('/kbbackup/archive/0000000100000016000000B5');解析完成后,查询解析结果视图,筛选出我们关心的表和操作类型:
SELECT lsn, xid, commit_time, schema_name, table_name, action, sql, undo_sql FROM walminer_logs WHERE table_name = 't_order' AND action = 'DELETE' ORDER BY lsn;结果里每一行代表一条变更记录。sql字段是该DELETE实际执行的语句形态,undo_sql字段就是反操作SQL,也就是把这行数据重新插回去的INSERT语句。把这些undo_sql汇总导出,就是一张可执行的恢复脚本。
第二步是生成恢复脚本。最简单的方法是把undo_sql字段直接导出到文件:
COPY ( SELECT undo_sql FROM walminer_logs WHERE table_name = 't_order' AND action = 'DELETE' ) TO '/tmp/restore_t_order.sql';但在实际执行前,我强烈建议先人工核对一下脚本内容,特别是确认undo_sql里没有包含其他不该恢复的事务影响。比如10点15分到10点40分之间,业务可能已经往t_order插入了新的数据,如果直接无脑执行INSERT,可能产生主键冲突。
第三步是用事务包裹的方式执行恢复,先不提交:
BEGIN; -- 把 /tmp/restore_t_order.sql 里的语句执行一遍 -- 然后查询业务关键数据,确认数量正确、主键不冲突 SELECT status, COUNT(*) FROM public.t_order GROUP BY status; -- 确认没问题再提交 COMMIT;如果发现主键冲突,说明原数据已经被重新插入过,那就得把恢复范围再缩小,只恢复缺失的那部分。整个过程不需要停库,业务可以继续运行。这也是WalMiner这类工具的最大价值:把“找数据”这个动作变成常规操作,而不是每次都搞出大动静。
4. 常见问题排查与避坑要点
4.1 解析结果为空,或者缺少关键SQL
这是初学者最容易遇到的问题。解析成功了,但walminer_logs里查不到任何记录,或者只有COMMIT、BEGIN之类的记录,没有实际增删改语句。
出现这种情况,第一反应检查wal_level。如果实例配置是minimal,WAL日志里根本不保留行级镜像,WalMiner再厉害也解析不出SQL。另一种可能是设置logical之前产生的WAL,这类旧日志同样存在信息不全的问题。所以配置wal_level一定要提前做,等到事故再改就已经晚了。
还有一种情况是解析范围没选对。比如误操作发生在10点15分,但解析时只扫了10点整之前的一个WAL文件,数据自然不会出现。排查方法是在walminer_logs里看看该表在所有解析结果里是否有更早或更晚的记录,确认时间范围覆盖是否正确。
4.2 WAL文件不完整或被清理
如果解析时报错提示找不到文件,或者文件被截断,说明归档目录或在线WAL没有覆盖到需要回溯的时间点。归档断档的原因通常有两个:一是archive_command失败,归档目录里缺文件;二是归档策略保留时间太短,旧文件被清理掉了。
处理办法是立刻检查归档目录的连续性:
-- 查看归档目录的文件列表和修改时间 ls -lrt /kbbackup/archive/ | tail -20如果缺失的WAL文件还能从备份里找回,就先把缺失文件补齐再解析。如果确认已经无法找回,那只能接受无法回溯到这个时间点的现实。所以日常运维中,归档健康度检查和WAL保留策略必须作为固定巡检项,不能等出事了再看。
4.3 解析大日志导致性能抖动
WalMiner解析时会在会话内产生大量临时数据,如果是解析整个目录或者跨多个文件,内存和临时表空间消耗会比较大。我在实践中遇到过解析超大归档目录时,临时文件把磁盘空间撑满的情况,幸好发现及时,否则会变成一个次生事故。
避免办法之一是尽量按LSN或文件范围解析,不要动不动全目录扫描。之二是解析前关注所在表空间和临时目录的剩余空间,预留至少与待解析WAL体积等量的空间。如果解析过程中发现速度明显变慢,可以考虑把结果集分批处理,一次只分析一个WAL文件,查完再清空结果集,再解析下一个。
4.4 权限和变更窗口
使用WalMiner需要管理员权限。这本身就意味着它不是普通开发账号能随便操作的工具,生产环境使用时必须走变更流程,并且在窗口内操作。解析过程中尽量不要和业务高峰期重叠,因为大量IO读取归档文件,可能对存储系统产生压力。
下表是几类常见问题速查:
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 扩展创建失败 | 版本不兼容、安装文件缺失 | 核对KingbaseES版本,重新安装匹配的walminer包 |
| 解析结果为空 | wal_level=minimal/replica、范围不对 | 修改wal_level=logical后重启,重新确认解析范围 |
| 找不到WAL文件 | 归档断档、文件被清理 | 补齐归档文件或从备份恢复对应WAL段 |
| 恢复脚本主键冲突 | 原数据已被重新写入 | 缩小恢复范围,仅恢复缺失行 |
| 解析了大半天不结束 | 解析范围过大、IO瓶颈 | 分段解析,按LSN或文件逐个处理 |
| 中文字段显示乱码 | 客户端编码不一致 | 连接时设置client_encoding=utf8,确认数据库字符集 |
5. 容易被忽视的细节
WalMiner使用起来并不复杂,但真正影响成败的往往是一些细节点。
时间线(Timeline)这个概念非常关键。做过PITR恢复的库,或者经历过多次恢复演练的库,其时间线编号可能已经大于1。解析时如果默认使用时间线1,很可能解析不到实际的数据。所以在调用walminer_read_walfile时,务必确认当前活跃的时间线编号,不确定可以查sys_wal目录下文件名的前8位,或者通过控制文件信息确认。
中文和二进制数据的显示也需要留意。解析结果里的sql和undo_sql字段,理论上是可以直接执行的,但如果字段里包含特殊字符、二进制大对象,或者字符集不一致,生成的SQL可能并不完美。尤其是高精度数值类型、JSONB这类复杂类型,建议不要盲目复制执行,而是先抽样检查几条,确认语句里的常量格式是否正确。
备库上不建议直接跑WalMiner解析。虽然备库也接收WAL文件,但备库通常不具备完整的事务上下文,有些需要回放的细节信息可能和主库不一致。我见过有人在备库上解析归档,结果某几张表的数据始终对不上,后来换到主库一查就解决了。如果需要做长时间跨度的分析,更好的方式是在一台独立的测试实例上,把归档文件拷贝过去再解析,避免对主库产生额外负担。
另外,WalMiner解析出的内容只能证明数据库确实执行过某些变更,但日志中不会记录是谁通过哪个客户端执行的操作,除非你在连接层做了额外审计。所以它更适合用来做“发生了什么”的定位,如果要做“谁干的”这类安全审计,还需要配合应用层的操作日志和连接日志一起看。
关于刷日志和保留策略,我再补充一句:解析归档时,数据库实例本身会继续产生新的WAL,这些新日志如果也归档到同一目录,可能会被后续的全量解析重复扫描。建议在归档目录里按日期建子目录,方便按时间范围精确解析,也能避免解析范围被后面新增的文件污染。
我自己在实际操作中,已经形成了一个固定习惯:每次重要版本变更或批量数据操作前,先记录一个LSN锚点和时间点;确认WalMiner扩展可用;同时确保归档保留策略能覆盖至少3天。这套组合拳打过几次漂亮的数据找回仗后,团队里已经把它写进了应急手册。最后再分享一个小技巧——恢复时如果数据量很大,不要指望一条条手工执行undo_sql,批量导出后用sys_restore配合事务脚本分批执行,边执行边校验行数和主键冲突,这样既稳又快。单独维护一个“误操作工具箱”,里面至少有WalMiner和对应版本的恢复模板,事故来临时你会感谢那个提前备课的自己。