有时在对一些故障进行分析时,我们需要知道归档日志中具体有哪些内容,此时我们就需要将对归档日志进行反解析后进行分析,当前DM数据库中的归档日志分析主要是通过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
执行结果如下:
从中可以看到每条redo日志的执行细节信息,包括SQL语句、执行时间等。
SQL语句部分和实际的执行语句并不一样,比如一条delete from test_tb where id>4的语句会变成以下的四条LogmnrRecord记录:
具体的对应关系还需要继续探究。
相同的Xid的记录中,必然是以START开始,COMMIT结束,每个记录的startTimestamp表示事务的开始时间,与第一条START记录的timestamp时间是相同的。
这种方式相对简单,在连接上数据库后直接使用数据库的系统包,分析后的结果会缓存在数据库中。
-- 添加分析文件
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();
执行结果:
| 对比点\分析方式 | 接口方式分析 | DBMS_LOGMNR包方式分析 |
|---|---|---|
| 执行位置 | 本地机器 | 数据库 |
| 便利程度 | 需要写代码 | 简单的几条SQL即可,还可以使用where条件过滤结果 |
| 是否可以并发 | 可以 | 不可以 |
| 使用灵活度 | 较灵活,可以使用提供的接口分析出结果后做后续分析 | 仅可以在START_LOGMNR和END_LOGMNR之间进行分析,如果要后续分析只能导出结果后继续分析 |
文章
阅读量
获赞
