日志挖掘(LogMiner)

最后更新: 2026-04-15 作者: Oracle Team
页面目录

第十五章:日志挖掘(LogMiner)

文档信息
  • 适用版本:Oracle 12c / 18c / 19c / 21c
  • 阅读时间:约 45 分钟
  • 前置知识:第十四章闪回技术

15.1 LogMiner 概述

15.1.1 LogMiner 功能

┌─────────────────────────────────────────────────────────────────┐
│                      LogMiner 功能                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  LogMiner 用于分析重做日志和归档日志中的变更信息:                    │
│                                                                 │
│  主要用途:                                                      │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │ 1. 审计数据库变更                                          │  │
│  │ 2. 追踪用户操作                                            │  │
│  │ 3. 恢复误删除数据                                          │  │
│  │ 4. 分析性能问题(定位执行慢的 SQL)                         │  │
│  │ 5. 同步测试数据库                                          │  │
│  │ 6. 确认特定变更(如谁在什么时候修改了某行)                  │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                 │
│  LogMiner 读取两类日志:                                          │
│  • 在线重做日志文件                                              │
│  • 归档日志文件                                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

15.1.2 前提条件

-- 1. 确保数据库处于归档模式(分析归档日志需要)
SELECT log_mode FROM v$database;
-- 应该返回 ARCHIVELOG

-- 2. 创建 LogMiner 需要的表空间和用户
-- sys 用户通常已有必要的权限

-- 3. 设置补充日志(用于追踪列变更)
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (UNIQUE) COLUMNS;

-- 4. 查看补充日志状态
SELECT SUPPLEMENTAL_LOG_DATA_PK, 
       SUPPLEMENTAL_LOG_DATA_UI, 
       SUPPLEMENTAL_LOG_DATA_ALL
FROM v$database;

15.2 LogMiner 配置

15.2.1 创建数据字典

-- 方式1:提取数据字典到重做日志
BEGIN
    DBMS_LOGMNR_D.BUILD(
        options => DBMS_LOGMNR_D.STORE_IN_REDO_LOG
    );
END;
/

-- 方式2:提取数据字典到文件系统
BEGIN
    DBMS_LOGMNR_D.BUILD(
        dictionary_filename => 'dictionary.ora',
        dictionary_location => '/u01/app/oracle/oradata/ORCL/',
        options => DBMS_LOGMNR_D.STORE_IN_FLAT_FILE
    );
END;
/

-- 查看数据字典文件
!ls -la /u01/app/oracle/oradata/ORCL/dictionary.ora

15.2.2 添加日志文件

-- 方式1:添加单个日志
BEGIN
    DBMS_LOGMNR.ADD_LOGFILE(
        logfilename => '/u01/app/oracle/arch/ORCL/arch_001.arc',
        options => DBMS_LOGMNR.NEW
    );
END;
/

-- 方式2:添加多个日志
BEGIN
    -- 添加第一个日志
    DBMS_LOGMNR.ADD_LOGFILE(
        logfilename => '/u01/app/oracle/arch/ORCL/arch_001.arc',
        options => DBMS_LOGMNR.NEW
    );
    
    -- 添加更多日志
    DBMS_LOGMNR.ADD_LOGFILE(
        logfilename => '/u01/app/oracle/arch/ORCL/arch_002.arc',
        options => DBMS_LOGMNR.ADDFILE
    );
END;
/

-- 方式3:自动发现所有归档日志
DECLARE
    v_log_files DBMS_APPLICATION_INFO.ATTR_CLOB;
BEGIN
    FOR rec IN (
        SELECT name FROM v$archived_log 
        WHERE name IS NOT NULL
        AND completion_time >= SYSDATE - 7
        ORDER BY sequence#
    ) LOOP
        DBMS_LOGMNR.ADD_LOGFILE(
            logfilename => rec.name,
            options => DBMS_LOGMNR.ADDFILE
        );
    END LOOP;
END;
/

15.3 启动 LogMiner 并分析

15.3.1 启动 LogMiner

-- 1. 基本启动
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        options => DBMS_LOGMNR.DICT_FROM_REDO_LOG
    );
END;
/

-- 2. 使用字典文件启动
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora',
        starttime => TO_DATE('2024-01-15 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
        endtime => TO_DATE('2024-01-15 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
    );
END;
/

-- 3. 基于 SCN 范围
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        startscn => 12345678,
        endscn => 12350000,
        dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora'
    );
END;
/

-- 4. 包含 SQL_REDO 和 SQL_UNDO
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora',
        starttime => TO_DATE('2024-01-15 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
        endtime => TO_DATE('2024-01-15 10:00:00', 'YYYY-MM-DD HH24:MI:SS'),
        options => DBMS_LOGMNR.COMMITTED_DATA_ONLY + 
                   DBMS_LOGMNR.STRING_literals_in_DATA
    );
END;
/

-- 5. 关闭 LogMiner
BEGIN
    DBMS_LOGMNR.END_LOGMNR;
END;
/

15.3.2 LogMiner 选项

选项 说明
DICT_FROM_REDO_LOG 从重做日志读取字典
STORE_IN_FLAT_FILE 将字典存储到文件
STORE_IN_REDO_LOG 将字典存储到重做日志
COMMITTED_DATA_ONLY 只显示已提交事务
NO_ROWID_IN_STMT SQL 语句中不包含 ROWID
STRING_LITERALS_IN_DATA 使用字符串格式显示数据

15.4 查询日志内容

15.4.1 基本查询

-- 查看所有变更
SELECT 
    timestamp,
    seg_owner,
    seg_name,
    operation,
    sql_redo,
    sql_undo,
    username
FROM v$logmnr_contents
ORDER BY timestamp;

-- 查看特定表的变更
SELECT 
    timestamp,
    operation,
    sql_redo,
    sql_undo,
    data_obj#,
    dataobj#,
    seg_owner,
    seg_name
FROM v$logmnr_contents
WHERE seg_name = 'EMPLOYEES'
AND seg_owner = 'HR'
ORDER BY timestamp;

-- 查看特定用户的操作
SELECT 
    timestamp,
    username,
    operation,
    sql_redo
FROM v$logmnr_contents
WHERE username = 'HR'
ORDER BY timestamp;

-- 查看 DML 操作
SELECT 
    timestamp,
    operation,
    seg_name,
    sql_redo
FROM v$logmnr_contents
WHERE operation IN ('INSERT', 'UPDATE', 'DELETE')
ORDER BY timestamp;

-- 查看 DDL 操作
SELECT 
    timestamp,
    operation,
    sql_redo,
    seg_name
FROM v$logmnr_contents
WHERE operation = 'DDL'
ORDER BY timestamp;

15.4.2 详细查询

-- 查看事务级别信息
SELECT 
    session#,
    serial#,
    username,
    transaction_name,
    COUNT(*) AS operation_count,
    MIN(timestamp) AS start_time,
    MAX(timestamp) AS end_time
FROM v$logmnr_contents
WHERE operation = 'INSERT'
GROUP BY session#, serial#, username, transaction_name
ORDER BY start_time DESC;

-- 查看特定 SCN 范围
SELECT 
    scn,
    timestamp,
    operation,
    sql_redo
FROM v$logmnr_contents
WHERE scn BETWEEN 12345678 AND 12350000
ORDER BY scn;

-- 查看回滚操作
SELECT 
    timestamp,
    operation,
    sql_undo
FROM v$logmnr_contents
WHERE operation = 'DELETE'
AND seg_name = 'EMPLOYEES'
ORDER BY timestamp;

-- 查看列值变化
SELECT 
    timestamp,
    operation,
    seg_name,
    DBMS_LOGMNR.COLUMN_PRESENT(
        row_data, 
        'SALARY'
    ) AS has_salary_change,
    row_data
FROM v$logmnr_contents
WHERE seg_name = 'EMPLOYEES'
AND operation = 'UPDATE';

15.5 实用案例

15.5.1 追踪误删除数据

-- 场景:某行数据被误删除,需要找回

-- 1. 启动 LogMiner(确定大概时间)
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora',
        starttime => TO_DATE('2024-01-15 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
        endtime => TO_DATE('2024-01-15 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
    );
END;
/

-- 2. 查找删除操作
SELECT 
    timestamp,
    operation,
    sql_undo,  -- 用于恢复的 INSERT 语句
    seg_name,
    session#,
    serial#
FROM v$logmnr_contents
WHERE operation = 'DELETE'
AND seg_name = 'EMPLOYEES'
AND seg_owner = 'HR'
AND sql_undo LIKE '%1000%'  -- 假设员工 ID 是 1000
ORDER BY timestamp;

-- 3. 执行 UNDO 语句恢复数据
-- 先查看 UNDO 内容
SELECT sql_undo FROM v$logmnr_contents
WHERE operation = 'DELETE'
AND seg_name = 'EMPLOYEES'
AND ROWNUM = 1;

-- 4. 手动执行恢复(复制 UNDO 内容执行)

-- 5. 结束 LogMiner
BEGIN
    DBMS_LOGMNR.END_LOGMNR;
END;
/

15.5.2 审计用户操作

-- 场景:审计 HR 用户的所有操作

-- 1. 分析日志
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora',
        starttime => TO_DATE('2024-01-15 00:00:00', 'YYYY-MM-DD HH24:MI:SS'),
        endtime => TO_DATE('2024-01-15 23:59:59', 'YYYY-MM-DD HH24:MI:SS'),
        options => DBMS_LOGMNR.COMMITTED_DATA_ONLY
    );
END;
/

-- 2. 汇总用户操作
SELECT 
    username,
    operation,
    COUNT(*) AS operation_count
FROM v$logmnr_contents
WHERE username = 'HR'
GROUP BY username, operation
ORDER BY COUNT(*) DESC;

-- 3. 查看具体操作时间线
SELECT 
    TO_CHAR(timestamp, 'YYYY-MM-DD HH24:MI:SS') AS time,
    operation,
    seg_name,
    SUBSTR(sql_redo, 1, 200) AS sql_statement
FROM v$logmnr_contents
WHERE username = 'HR'
ORDER BY timestamp;

-- 4. 查看高风险操作(TRUNCATE、DROP)
SELECT 
    timestamp,
    operation,
    seg_name,
    sql_redo
FROM v$logmnr_contents
WHERE username = 'HR'
AND operation IN ('TRUNCATE', 'DROP')
ORDER BY timestamp;

BEGIN
    DBMS_LOGMNR.END_LOGMNR;
END;
/

15.5.3 同步测试数据库

-- 场景:将生产库的变更同步到测试库

-- 1. 分析需要的日志
BEGIN
    DBMS_LOGMNR.ADD_LOGFILE(
        logfilename => '/u01/app/oracle/arch/PROD/arch_001.arc',
        options => DBMS_LOGMNR.NEW
    );
    
    DBMS_LOGMNR.ADD_LOGFILE(
        logfilename => '/u01/app/oracle/arch/PROD/arch_002.arc',
        options => DBMS_LOGMNR.ADDFILE
    );
END;
/

-- 2. 启动 LogMiner
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => '/u01/app/oracle/oradata/PROD/dictionary.ora'
    );
END;
/

-- 3. 提取 SQL 语句
SELECT 
    'execute immediate ''' || REPLACE(sql_redo, '''', '''''') || ''';'
FROM v$logmnr_contents
WHERE operation IN ('INSERT', 'UPDATE', 'DELETE')
AND seg_owner = 'HR'
ORDER BY timestamp;

-- 4. 结束
BEGIN
    DBMS_LOGMNR.END_LOGMNR;
END;
/

15.5.4 定位问题 SQL

-- 场景:某时段性能下降,需要定位问题 SQL

-- 1. 启动 LogMiner
BEGIN
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora',
        starttime => TO_DATE('2024-01-15 14:00:00', 'YYYY-MM-DD HH24:MI:SS'),
        endtime => TO_DATE('2024-01-15 15:00:00', 'YYYY-MM-DD HH24:MI:SS')
    );
END;
/

-- 2. 按操作类型统计
SELECT 
    operation,
    COUNT(*) AS count,
    SUM(LENGTH(sql_redo)) AS total_sql_length
FROM v$logmnr_contents
GROUP BY operation
ORDER BY count DESC;

-- 3. 找出最复杂的 UPDATE
SELECT 
    timestamp,
    seg_name,
    LENGTH(sql_redo) AS sql_length,
    SUBSTR(sql_redo, 1, 300) AS sql_preview
FROM v$logmnr_contents
WHERE operation = 'UPDATE'
ORDER BY LENGTH(sql_redo) DESC
FETCH FIRST 10 ROWS ONLY;

-- 4. 找出高频小操作
SELECT 
    username,
    seg_name,
    operation,
    COUNT(*) AS execution_count,
    MIN(sql_redo) AS sample_sql
FROM v$logmnr_contents
WHERE operation IN ('INSERT', 'UPDATE', 'DELETE')
GROUP BY username, seg_name, operation
HAVING COUNT(*) > 100
ORDER BY COUNT(*) DESC;

BEGIN
    DBMS_LOGMNR.END_LOGMNR;
END;
/

15.6 最佳实践

15.6.1 性能优化

-- 1. 限制分析范围
-- 尽量指定明确的 starttime/endtime 或 startscn/endscn

-- 2. 使用 COMMITTED_DATA_ONLY
-- 只显示已提交事务,减少数据量

-- 3. 定期清理日志
-- 旧日志分析价值降低,可以定期清理

-- 4. 使用 DBMS_LOGMNR.STORE_IN_REDO_LOG
-- 将字典信息存储在日志中,便于后续分析

-- 5. 大日志分析
-- 如果日志很大,可以分批分析
DECLARE
    v_start_scn NUMBER := 12345678;
    v_end_scn NUMBER;
    v_batch_size NUMBER := 10000;
BEGIN
    FOR i IN 1..100 LOOP
        v_end_scn := v_start_scn + v_batch_size;
        
        DBMS_LOGMNR.START_LOGMNR(
            startscn => v_start_scn,
            endscn => v_end_scn,
            dictfilename => '/u01/app/oracle/oradata/ORCL/dictionary.ora'
        );
        
        -- 分析操作...
        -- SELECT COUNT(*) FROM v$logmnr_contents;
        
        DBMS_LOGMNR.END_LOGMNR;
        
        v_start_scn := v_end_scn + 1;
    END LOOP;
END;
/

15.6.2 完整分析流程

-- 完整 LogMiner 分析流程
SET SERVEROUTPUT ON
DECLARE
    v_start_time DATE := TO_DATE('2024-01-15 09:00:00', 'YYYY-MM-DD HH24:MI:SS');
    v_end_time DATE := TO_DATE('2024-01-15 10:00:00', 'YYYY-MM-DD HH24:MI:SS');
    v_dict_loc VARCHAR2(100) := '/u01/app/oracle/oradata/ORCL/';
    v_dict_file VARCHAR2(100) := v_dict_loc || 'dictionary.ora';
    v_count NUMBER;
BEGIN
    -- Step 1: 创建字典文件
    DBMS_OUTPUT.PUT_LINE('Building dictionary...');
    DBMS_LOGMNR_D.BUILD(
        dictionary_filename => v_dict_file,
        dictionary_location => v_dict_loc,
        options => DBMS_LOGMNR_D.STORE_IN_FLAT_FILE
    );
    
    -- Step 2: 添加日志文件
    DBMS_OUTPUT.PUT_LINE('Adding log files...');
    FOR rec IN (
        SELECT name FROM v$archived_log 
        WHERE completion_time BETWEEN v_start_time AND v_end_time
        AND name IS NOT NULL
    ) LOOP
        IF SQL%ROWCOUNT = 0 THEN
            DBMS_LOGMNR.ADD_LOGFILE(rec.name, DBMS_LOGMNR.NEW);
        ELSE
            DBMS_LOGMNR.ADD_LOGFILE(rec.name, DBMS_LOGMNR.ADDFILE);
        END IF;
    END LOOP;
    
    -- Step 3: 启动 LogMiner
    DBMS_OUTPUT.PUT_LINE('Starting LogMiner...');
    DBMS_LOGMNR.START_LOGMNR(
        dictfilename => v_dict_file,
        starttime => v_start_time,
        endtime => v_end_time,
        options => DBMS_LOGMNR.DICT_FROM_REDO_LOG + 
                   DBMS_LOGMNR.COMMITTED_DATA_ONLY
    );
    
    -- Step 4: 分析操作
    DBMS_OUTPUT.PUT_LINE('Analyzing operations...');
    SELECT COUNT(*) INTO v_count FROM v$logmnr_contents;
    DBMS_OUTPUT.PUT_LINE('Total operations: ' || v_count);
    
    -- Step 5: 输出结果
    FOR rec IN (
        SELECT timestamp, username, operation, sql_redo 
        FROM v$logmnr_contents
        WHERE ROWNUM <= 100
        ORDER BY timestamp
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(
            rec.timestamp || ' | ' || rec.username || 
            ' | ' || rec.operation || ' | ' || 
            SUBSTR(rec.sql_redo, 1, 100)
        );
    END LOOP;
    
    -- Step 6: 结束
    DBMS_LOGMNR.END_LOGMNR;
    DBMS_OUTPUT.PUT_LINE('LogMiner completed.');
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_LOGMNR.END_LOGMNR;
        RAISE;
END;
/

15.7 本章小结

✅ LogMiner 功能:分析重做日志和归档日志中的变更
✅ 前提条件:归档模式、补充日志
✅ 数据字典:创建和存储方式
✅ 日志添加:单个/多个日志,自动发现
✅ 启动选项:时间/SCN 范围,提交数据过滤
✅ 查询内容:sql_redo/sql_undo/操作类型
✅ 实用场景:误删除恢复、审计追踪、问题定位

📖 下章预告:高可用方案

下一步:学习 第十六章:高可用方案