注册
使用LOGMNR解析归档日志的实践
技术分享/ 文章详情 /

使用LOGMNR解析归档日志的实践

BruceCD 2025/10/17 160 0 0

概述

有时在对一些故障进行分析时,我们需要知道归档日志中具体有哪些内容,此时我们就需要将对归档日志进行反解析后进行分析,当前DM数据库中的归档日志分析主要是通过LOGMNR来进行,分析方式有以下两种:

  1. 使用LOGMNR的编程接口
  2. 使用DBMS_LOGMNR系统包

针对这两种方法,我会分别进行说明使用步骤,并比较它们的优劣。

LOGMNR编程接口

LOGMNR有两种语言的编程接口,分别是C和java,而java的实现是用jni的方式来调用C的动态库,两种最终用的最底层的分析接口是一样的。
所以使用的话仅需按照编程习惯选择其中一种即可,以下我们用java的jni方式进行说明。

jni接口 C接口 说明
LogmnrDll.initLogmnr() logmnr_client_init() 初始化LOGMNR环境
LogmnrDll.createConnect() logmnr_client_create_connect() 创建分析日志的连接
LogmnrDll.addLogFile() logmnr_client_add_logfile() 增加日志文件
LogmnrDll.removeLogFile() logmnr_client_remove_logfile() 删除日志文件
LogmnrDll.startLogmnr() logmnr_client_start() 启动日志分析
LogmnrDll.getData() logmnr_client_get_data() 获取数据
LogmnrDll.endLogmnr() logmnr_client_end() 终止日志分析
LogmnrDll.closeConnect() logmnr_client_close_connect() 结束连接
LogmnrDll.deinitLogmnr() logmnr_client_deinit() 销毁环境
LogmnrDll.setAttr() logmnr_client_set_attr() 设置日志分析属性
LogmnrDll.getAttr() logmnr_client_get_attr() 设置日志分析属性

这些接口名称虽然名称不都一样,但是都是一一对应的,并且参数也是可以相互参考的。

对于分析出来的结果记录,每一条都是对应一个单独的redo日志,它在java中是对应的LogmnrRecord对象,有Xid、sqlRedo、scn、startScn、commitScn等属性,具体含义可以参考官方文档,我们主要关注以下几个:
Xid表示事务的ID号
sqlRedo表示执行的SQL语句

以下代码是在使用手册中的示例代码做过一些修改后,主要修改点是在LogmnrDll.addLogFile()函数可以调用多次,将归档日志进行累计后分析;以及LogmnrDll.getData()是一个流式接口,如果结果集比较大,需要去循环调用它,每次调用时都是从上一次记录后的下一条开始获取。

将以下代码保存为Testlogmnr.java

import java.io.PrintStream; import com.dameng.logmnr.LogmnrDll; import com.dameng.logmnr.LogmnrRecord; public class TestLogmnr { public static void main(String[] args) { try { // 初始化LOGMNR环境 LogmnrDll.initLogmnr(); // 建立分析日志的连接,需要注意分析日志文件和此连接需要是在同一个数据库上,否则分析结果会有问题 long connid = LogmnrDll.createConnect("localhost", 5236, "SYSDBA", "Sysdba@123"); // 添加多个待分析的归档日志文件,具体文件路径需要看实际的路径和文件名 LogmnrDll.addLogFile(connid,"/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_13-22-54.log",3); LogmnrDll.addLogFile(connid,"/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_13-29-30.log",3); LogmnrDll.addLogFile(connid,"/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_16-20-13.log",3); LogmnrDll.addLogFile(connid,"/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_16-34-47.log",3); LogmnrDll.addLogFile(connid,"/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_16-58-16.log",3); LogmnrDll.addLogFile(connid,"/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_17-07-14.log",3); // 设置并发线程数 LogmnrDll.setAttr(connid, LogmnrDll.LOGMNR_ATTR_PARALLEL_NUM, 8); // 开始执行分析 LogmnrDll.startLogmnr(connid, -1, null, null); LogmnrRecord[] arr; // 定义getData一次最大的数量 int batch_size = 10; int batch_count = 0; while (true) { // 流式获取分析结果,一次最多获取batch_size个 arr = LogmnrDll.getData(connid, batch_size); System.out.println("日志分析结果打印:"); int offset = batch_size * batch_count; for (int i = 0; i < arr.length; i++) { System.out.print("-----------------------------" + (i + offset) + "-----------------------------" + "\n"); System.out.print("-- # xid:" + arr[i].getXid() + "\n"); System.out.print("-- # operation:" + arr[i].getOperation() + "\n"); System.out.print("-- # sqlRedo:" + arr[i].getSqlRedo() + "\n"); System.out.print("-- # scn:" + arr[i].getScn() + "\n"); System.out.print("-- # startScn:" + arr[i].getStartScn() + "\n"); System.out.print("-- # commitScn:" + arr[i].getCommitScn() + "\n"); System.out.print("-- # timestamp:" + arr[i].getTimestamp() + "\n"); System.out.print("-- # startTimestamp:" + arr[i].getStartTimestamp() + "\n"); System.out.print("-- # commitTimestamp:" + arr[i].getCommitTimestamp() + "\n"); System.out.print("-- # operationCode:" + arr[i].getOperationCode() + "\n"); System.out.print("-- # rollBack:" + arr[i].getRollBack() + "\n"); System.out.print("-- # segOwner:" + arr[i].getSegOwner() + "\n"); System.out.print("-- # tableName:" + arr[i].getTableName() + "\n"); System.out.print("-- # rowId:" + arr[i].getRowId() + "\n"); System.out.print("-----------------------------" + (i + offset) + "-----------------------------" + "\n"); System.out.print("\n"); } // 如果本次getData获取到的记录数少于batch_size,说明是最后一批了,直接退出循环 if (arr.length < batch_size) { break; } batch_count += 1; } System.out.println("结果打印完毕"); // 终止日志分析 LogmnrDll.endLogmnr(connid, 1); // 销毁分析环境 LogmnrDll.deinitLogmnr(); } catch (Exception e) { e.printStackTrace(); } } }

然后执行编译运行命令:

# 编译 javac -cp $DM_HOME/jar/com.dameng.logmnr.jar TestLogmnr.java # 运行 java -cp .:$DM_HOME/jar/com.dameng.logmnr.jar TestLogmnr

执行结果如下:
image.png

从中可以看到每条redo日志的执行细节信息,包括SQL语句、执行时间等。

SQL语句部分和实际的执行语句并不一样,比如一条delete from test_tb where id>4的语句会变成以下的四条LogmnrRecord记录:
image.png
具体的对应关系还需要继续探究。

相同的Xid的记录中,必然是以START开始,COMMIT结束,每个记录的startTimestamp表示事务的开始时间,与第一条START记录的timestamp时间是相同的。

DBMS_LOGMNR系统包方式

这种方式相对简单,在连接上数据库后直接使用数据库的系统包,分析后的结果会缓存在数据库中。

-- 添加分析文件 DBMS_LOGMNR.ADD_LOGFILE('/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_17-18-00.log',OPTIONS=>DBMS_lOGMNR.ADDFILE); DBMS_LOGMNR.ADD_LOGFILE('/data/dmdata/arch/ARCHIVE_LOCAL1_0x19B6CE78_EP0_2025-10-09_17-22-11.log',OPTIONS=>DBMS_lOGMNR.ADDFILE); -- 开始分析 dbms_LOGMNR.START_LOGMNR(); -- v$logmnr_content视图只能在START_LOGMNR和END_LOGMNR之间调用,否则会报错 select * from V$LOGMNR_CONTENTS; -- 结束分析 DBMS_LOGMNR.END_LOGMNR();

执行结果:
image.png

对比

对比点\分析方式 接口方式分析 DBMS_LOGMNR包方式分析
执行位置 本地机器 数据库
便利程度 需要写代码 简单的几条SQL即可,还可以使用where条件过滤结果
是否可以并发 可以 不可以
使用灵活度 较灵活,可以使用提供的接口分析出结果后做后续分析 仅可以在START_LOGMNR和END_LOGMNR之间进行分析,如果要后续分析只能导出结果后继续分析

问题解决

  1. 在分析归档日志文件时,如果在执行完DML或者DDL操作后立即进行分析,可能无法看到对应的操作信息,需要等一段时间后才能看到。
    这个问题是因为归档日志是有缓存的,当缓存使用量达到ARCH_FLUSH_BUF_SIZE参数配置的大小后会进行刷盘,从归档日志的修改时间上判断,确实是有很大的延迟,如果将ARCH_FLUSH_BUF_SIZE参数修改为0后重启数据库,再次验证就发现执行完成的DML操作可以立即被分析到。
  2. 在执行分析时必须要开启数据库的RLOG_APPEND_LOGIC参数必须为非0值,否则生成的redo日志会被直接忽略跳过。

疑问

  1. 为什么addFile接口有删除文件的功能?这样还需要removeFile接口吗?感觉有些鸡肋
  2. ARCH_FLUSH_BUF_SIZE参数如果设置得比较大,那是要等缓存满了才会刷本地日志盘吗?是否还有其他方式刷盘?
评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服