Oracle 重做日志(Redo Log)机制详解

Oracle 重做日志(Redo Log)机制详解

适用版本:Oracle Database 19c / 23ai 阅读基础:了解 Oracle 后台进程 LGWR、DBWn 文档版本:v1.0 / 2026-07


目录


1. 概述:Redo Log 是 ACID 持久性的核心保障

重做日志(Redo Log)是 Oracle 数据库实现事务 ACID 特性中**持久性(Durability)**的核心机制[1][2]。

核心作用

  1. 实例恢复:异常关机后,重做日志用于恢复未持久化的事务
  2. 介质恢复:数据文件损坏后,结合备份和归档日志恢复数据
  3. 变更追踪:用于 Data Guard、GoldenGate 等数据同步

底层原理:Oracle 采用 WAL(Write-Ahead Logging,预写式日志) 策略[1][2]——所有数据修改必须先记录到日志并落盘,然后才允许数据文件更新。

核心特点

  • 二进制文件:记录变更向量,不是 SQL 文本
  • 顺序写入:LGWR 进程顺序追加写入,I/O 效率极高
  • 循环使用:至少需要 2 个日志组
  • 多路复用:每个组可以有多个成员(镜像)

2. Redo Log 的核心概念

2.1 重做记录(Redo Record)

重做记录(Redo Entry)是 LGWR 写入的最小单位[2]。

记录内容

  • 变更的数据块地址
  • 变更前的前映像(Before Image)
  • 变更后的后映像(After Image)
  • 事务 ID(XID)
  • SCN(System Change Number)

示例

UPDATE emp SET sal = 5000 WHERE empno = 7900;

对应 Redo Record:
- 数据块地址:file=4, block=1234
- 偏移量:56
- 前映像:800(旧 sal 值)
- 后映像:5000(新 sal 值)
- 事务 ID:8.12.1234
- SCN:1234567

2.2 变化向量(Change Vector)

变化向量是重做记录的组成单元[2],描述对单个块的修改。

一条 UPDATE 可能产生多个变化向量

  • 数据段块变化(表中数据)
  • Undo 段块变化(记录前映像)
  • 事务表变化(事务槽更新)
  • 索引块变化(如修改了索引列)

2.3 日志组(Group)和成员(Member)

日志组(Group)
  ├── 成员 1(Member 1)  -- /u01/oradata/orcl/redo01a.log
  ├── 成员 2(Member 2)  -- /u02/oradata/orcl/redo01b.log
  └── 成员 3(Member 3)  -- /u03/oradata/orcl/redo01c.log

Group

  • 逻辑概念
  • 包含一个或多个完全相同的成员(镜像)
  • LGWR 同时写入组内所有成员

Member

  • 物理文件
  • 同一组内的成员内容完全相同
  • 用于冗余,提高可用性

最小配置:至少 2 个组,每组至少 1 个成员。

生产配置:3+ 个组,每组 2-3 个成员(分布在不同磁盘)。

2.4 日志循环使用

   ┌── Group 1 ──┐
   │              │
   ↓              │
写满 → 切换        │
   │              │
   ↓              │
   Group 2 ──→ Group 3

   └→ Group 1(循环回来,覆盖旧数据)

关键特性[1][2]:

  • 当前活动组(CURRENT)接收所有新产生的重做记录
  • 写满后触发日志切换(Log Switch)
  • 切换后变为 ACTIVE 状态,等待检查点完成
  • 检查点完成后变为INACTIVE,可被覆盖
  • 最终回到 UNUSED 队列等待重用

3. Redo Log 写入机制

3.1 LGWR 触发条件

LGWR(Log Writer)进程在以下情况触发写操作[1][3]:

触发条件说明
用户提交事务(COMMIT)确保已提交数据持久化
Redo Log Buffer 达 1/3 满防止缓冲区溢出
Redo Log Buffer 有 1MB 脏数据即使不满 1/3
每 3 秒超时定时写入
DBWn 写脏块前WAL 原则:先记日志,再写数据
检查点发生时同步日志和数据

坑 1:LGWR 写入性能直接影响数据库吞吐量,是 OLTP 系统最关键的性能瓶颈之一。

3.2 WAL(Write-Ahead Logging)原则

核心原则[2]:

数据修改流程:
  1. 用户进程修改数据块(Buffer Cache)
  2. 生成 Redo Record 写入 Redo Log Buffer
  3. COMMIT 时 LGWR 写入 Redo Log 文件
  4. DBWn 异步写入数据文件(可能滞后很久)

关键点

  • DBWn 写数据文件前,必须先 LGWR 写完 Redo Log
  • 这确保了:即使数据文件未更新,但日志已持久化,可通过日志恢复
  • 提交返回成功标志前,必须等 LGWR 完成写入(产生 log file sync 等待)

3.3 写入流程

用户会话                     LGWR                      磁盘
   │                          │                          │
   ├─→ 修改 Buffer Cache       │                          │
   ├─→ 生成 Redo Record        │                          │
   ├─→ 写入 Redo Log Buffer    │                          │
   │                          │                          │
   ├─→ COMMIT                  │                          │
   │                          │                          │
   │                          ├─→ 读取 Redo Log Buffer    │
   │                          ├─→ 写入 Redo Log File      │
   │                          │                          ├─→ fsync
   │                          │                          │
   │                          ├─→ 写入完成通知            │
   │                          │                          │
   ├─→ 收到 COMMIT 成功        │                          │
   │                          │                          │

log file sync 等待事件[3]:用户会话等待 LGWR 完成写入,是 OLTP 系统常见性能瓶颈。


4. 日志切换与检查点

4.1 日志切换(Log Switch)

日志切换指 LGWR 停止写当前日志组,开始写下一个日志组[2]。

触发方式

  • 自动切换:当前日志组写满
  • 手动切换ALTER SYSTEM SWITCH LOGFILE;

切换过程

  1. LGWR 停止写当前组
  2. 选择下一个可用组
  3. 当前组状态变为 ACTIVE
  4. 触发检查点(Checkpoint)
  5. 启动 ARCn 进程归档(如 ARCHIVELOG 模式)
  6. LGWR 开始写新组

4.2 检查点(Checkpoint)

检查点是 Oracle 同步内存和数据文件的关键操作[2]。

检查点类型

类型触发时机说明
完全检查点SHUTDOWN IMMEDIATE、ALTER SYSTEM CHECKPOINT所有脏块写入数据文件
日志切换检查点日志切换时写入切换组的脏块
增量检查点持续运行按 RBA 顺序渐进写入
局部检查点表空间离线、热备特定数据文件的脏块

检查点作用

  • 减少实例恢复时间
  • 同步数据文件头与控制文件
  • 释放 redo log 空间

4.3 Checkpoint not complete 故障

现象[3]:

Alert Log 显示:
Thread 1 cannot allocate new log, sequence 1234
Checkpoint not complete
Current log# 1 seq# 1233 mem# 0: /u01/oradata/orcl/redo01.log

原因[3]:

  • LGWR(生产者)跑得太快,绕了一圈回来
  • DBWR(消费者)还没把对应脏块写入数据文件
  • 为了数据一致性,Oracle 强制挂起所有写入操作
  • 业务表现为:数据库突然卡顿,DML 全部等待

解决

  1. 临时解决:手动切换 + 检查点

    ALTER SYSTEM SWITCH LOGFILE;
    ALTER SYSTEM CHECKPOINT;
  2. 根本解决:增加日志组数和大小

    ALTER DATABASE ADD LOGFILE GROUP 4 '/u01/oradata/orcl/redo04.log' SIZE 1G;
    ALTER DATABASE ADD LOGFILE GROUP 5 '/u01/oradata/orcl/redo05.log' SIZE 1G;
  3. 优化 DBWn 性能:增加 DBWR 进程数

    ALTER SYSTEM SET db_writer_processes = 4 SCOPE=SPFILE;

5. Redo Log 状态详解

状态流转:
UNUSED → CURRENT → ACTIVE → INACTIVE → (循环回 CURRENT)
状态含义可否覆盖
UNUSED新建的、从未使用
CURRENTLGWR 正在写入的组
ACTIVE还没完成检查点,崩溃恢复需要
INACTIVE检查点完成,可被覆盖
CLEARING正在被清空(CLEAR 命令)
CLEARING_CURRENT当前正在被清空

查询

SELECT group#, status, sequence#, bytes/1024/1024 AS size_mb, members, 
       first_change#, first_time
FROM v$log
ORDER BY group#;

6. Redo Log 配置最佳实践

6.1 日志组数量

场景推荐组数
小型 OLTP3
中型 OLTP4-5
大型 OLTP5-8
DSS/OLAP3-4

6.2 日志文件大小

目标:每 10-30 分钟切换一次日志。

场景推荐大小
小型50-100 MB
中型200-500 MB
大型1-4 GB
Exadata4-8 GB

估算公式

日志大小 = (每秒 Redo 生成量 × 1800 秒)
       ≈ 30 分钟 Redo 量

查询 Redo 生成速率

SELECT 
    TO_CHAR(first_time, 'YYYY-MM-DD HH24') AS hour,
    SUM(blocks * block_size) / 1024 / 1024 / 1024 AS redo_gb
FROM v$archived_log
WHERE first_time > SYSDATE - 7
GROUP BY TO_CHAR(first_time, 'YYYY-MM-DD HH24')
ORDER BY hour;

6.3 多路复用

-- 推荐:每组 2-3 个成员,分布在不同磁盘
ALTER DATABASE ADD LOGFILE GROUP 1 
  ('/u01/oradata/orcl/redo01a.log',
   '/u02/oradata/orcl/redo01b.log') SIZE 500M;

6.4 日志文件位置

  • 与数据文件、控制文件分开存放
  • 放在高性能磁盘(SSD)
  • 与归档日志放不同磁盘

7. Redo Log 相关视图

7.1 v$log

SELECT group#, thread#, sequence#, bytes, members, 
       archived, status, first_change#, first_time
FROM v$log;

字段说明

字段含义
GROUP#日志组号
THREAD#线程号(RAC)
SEQUENCE#日志序列号
BYTES大小(字节)
MEMBERS成员数
ARCHIVED是否已归档(YES/NO)
STATUS状态(CURRENT/ACTIVE/INACTIVE)
FIRST_CHANGE#第一条记录的 SCN
FIRST_TIME第一条记录的时间

7.2 v$logfile

SELECT group#, status, type, member, is_recovery_dest_file
FROM v$logfile
ORDER BY group#, member;

status 字段

状态含义
INVALID文件不可访问
STALE文件内容不完整
DELETED文件已删除
空白文件正常

7.3 v$log_history

SELECT recid, thread#, sequence#, first_change#, next_change#, 
       first_time, next_time
FROM v$log_history
ORDER BY sequence# DESC
FETCH FIRST 20 ROWS ONLY;

7.4 v$archived_log

SELECT name, sequence#, first_change#, next_change#, 
       completion_time, blocks, block_size
FROM v$archived_log
WHERE completion_time > SYSDATE - 1
ORDER BY sequence# DESC;

8. Redo Log 管理操作

8.1 添加日志组

-- 单成员
ALTER DATABASE ADD LOGFILE GROUP 4 
  '/u01/oradata/orcl/redo04.log' SIZE 500M;

-- 多成员
ALTER DATABASE ADD LOGFILE GROUP 4 
  ('/u01/oradata/orcl/redo04a.log',
   '/u02/oradata/orcl/redo04b.log') SIZE 500M;

8.2 添加日志成员

ALTER DATABASE ADD LOGFILE MEMBER 
  '/u02/oradata/orcl/redo01b.log' TO GROUP 1;

8.3 删除日志组

-- 删除前确保不是 CURRENT 或 ACTIVE
ALTER DATABASE DROP LOGFILE GROUP 4;

-- 手工删除 OS 文件
-- rm /u01/oradata/orcl/redo04.log

坑 2:不能删除 CURRENT 组,需先切换:

ALTER SYSTEM SWITCH LOGFILE;
ALTER DATABASE DROP LOGFILE GROUP 4;

8.4 删除日志成员

ALTER DATABASE DROP LOGFILE MEMBER '/u02/oradata/orcl/redo01b.log';

8.5 重做日志文件重定位

-- 1. 关闭数据库
SHUTDOWN IMMEDIATE;

-- 2. OS 层移动文件
-- mv /u01/oradata/orcl/redo01.log /u04/oradata/orcl/redo01.log

-- 3. 启动 mount
STARTUP MOUNT;

-- 4. 重命名
ALTER DATABASE RENAME FILE '/u01/oradata/orcl/redo01.log' 
TO '/u04/oradata/orcl/redo01.log';

-- 5. 打开数据库
ALTER DATABASE OPEN;

8.6 清空损坏的日志文件

-- 清空日志组(不归档)
ALTER DATABASE CLEAR LOGFILE GROUP 3;

-- 清空未归档的日志组(需用 UNARCHIVED)
ALTER DATABASE CLEAR UNARCHIVED LOGFILE GROUP 3;

坑 3:清空未归档日志会导致数据丢失(如果该日志包含已提交事务),需立即做全备。


9. Redo Log 性能优化

9.1 监控关键等待事件

SELECT event, total_waits, time_waited, average_wait
FROM v$system_event
WHERE event IN (
    'log file sync',
    'log file parallel write',
    'log file switch (checkpoint incomplete)',
    'log file switch (archiving needed)',
    'redo log space wait',
    'redo buffer allocation retries'
)
ORDER BY time_waited DESC;

9.2 优化 log file sync

原因:用户会话等待 LGWR 完成写入。

优化方向

  1. 提升 Redo 日志 I/O 性能:换 SSD
  2. 减少提交频率:批量提交
  3. 优化 LGWR:避免多个 LGWR 争用
  4. 使用 NOLOGGING 操作:批量加载

9.3 Redo Log Buffer 调优

-- 查看当前值
SHOW PARAMETER log_buffer

-- 调优
ALTER SYSTEM SET log_buffer = 256M SCOPE=SPFILE;
-- 重启生效

监控

-- Redo Buffer 分配重试(应接近 0)
SELECT name, value 
FROM v$sysstat 
WHERE name = 'redo buffer allocation retries';

9.4 NOLOGGING 操作

-- 批量加载用 NOLOGGING 减少日志
ALTER TABLE big_table NOLOGGING;

-- INSERT 加 hint
INSERT /*+ APPEND */ INTO big_table SELECT * FROM source_table;

-- 索引创建
CREATE INDEX idx_name ON big_table(col) NOLOGGING;

坑 4:NOLOGGING 操作后必须立即备份,否则介质恢复时该数据会丢失。


10. 常见坑与排错

坑 1:Checkpoint not complete

现象:业务卡顿,alert log 报错。

解决:见 4.3 节,增加日志组数和大小。

坑 2:ORA-00312 无法访问联机日志

现象

ORA-00312: online log 1 thread 1: '/u01/oradata/orcl/redo01.log'
ORA-27041: unable to open file

解决

# 检查文件权限
ls -l /u01/oradata/orcl/redo01.log

# 如有副本,复制
cp /u02/oradata/orcl/redo01b.log /u01/oradata/orcl/redo01.log

# 如无副本,且非 CURRENT,清空
sqlplus / as sysdba
SQL> ALTER DATABASE CLEAR LOGFILE GROUP 1;

坑 3:CURRENT 日志组损坏

现象:数据库无法启动。

解决

-- 不完全恢复(会丢数据)
SQL> STARTUP MOUNT;
SQL> RECOVER DATABASE UNTIL CANCEL;
SQL> ALTER DATABASE OPEN RESETLOGS;

-- 立即做全备

坑 4:日志组过小

现象:日志切换频繁(每分钟切换多次)。

解决

-- 查看切换频率
SELECT 
    TO_CHAR(first_time, 'YYYY-MM-DD HH24:MI') AS time,
    COUNT(*) AS switches
FROM v$log_history
WHERE first_time > SYSDATE - 1
GROUP BY TO_CHAR(first_time, 'YYYY-MM-DD HH24:MI')
ORDER BY time;

-- 加大日志组
ALTER DATABASE ADD LOGFILE GROUP 4 '/u01/oradata/orcl/redo04.log' SIZE 2G;

坑 5:日志文件 I/O 瓶颈

现象log file parallel write 等待高。

解决

-- 1. 查看日志文件 I/O 性能
SELECT file#, phyrds, phywrts, phyblkrd, phyblkwrt
FROM v$filestat
WHERE file# IN (SELECT file# FROM v$logfile);

-- 2. 换 SSD 或独立磁盘

坑 6:成员丢失导致写失败

现象:LGWR 报错,数据库挂起。

解决

-- 1. 查看状态
SELECT group#, status, member FROM v$logfile;

-- 2. 删除失效成员
ALTER DATABASE DROP LOGFILE MEMBER '/u02/oradata/orcl/redo01b.log';

-- 3. 添加新成员
ALTER DATABASE ADD LOGFILE MEMBER '/u04/oradata/orcl/redo01b.log' TO GROUP 1;

坑 7:归档日志目标空间不足

现象log file switch (archiving needed) 等待。

解决

# 1. 检查归档目标空间
df -h /u01/arch

# 2. 清理旧归档日志
rman target /
RMAN> CROSSCHECK ARCHIVELOG ALL;
RMAN> DELETE EXPIRED ARCHIVELOG ALL;
RMAN> DELETE ARCHIVELOG ALL COMPLETED BEFORE 'SYSDATE-7';

11. 最佳实践

  1. 至少 3 个日志组:避免 Checkpoint not complete[3]
  2. 每组 2-3 个成员:分布在不同磁盘
  3. 日志大小让切换间隔 10-30 分钟:避免频繁切换
  4. 日志放独立高性能磁盘:SSD 优先
  5. 监控日志切换频率:每小时 5-10 次为宜
  6. 监控关键等待事件:log file sync、log file parallel write
  7. 批量操作用 NOLOGGING:减少 Redo 生成
  8. 不要把所有日志放同磁盘:单点故障
  9. 日志文件和归档日志分开:避免 I/O 争用
  10. RAC 每个实例至少 2 个组:线程独立

12. 参考资料

[1] 墨天轮,暮雨,《Oracle 体系结构-日志文件汇总》: https://www.modb.pro/db/1970883931515924480

[2] Oracle Database 19c Administrator’s Guide,Managing the Redo Log: https://docs.oracle.com/en/database/oracle/oracle-database/19/admin/managing-the-redo-log.html

[3] 墨天轮,安呀智数据坊,《Oracle 数据库突然卡顿?警惕 Checkpoint not complete 导致的性能雪崩》: https://www.modb.pro/db/2031315603437477888

[4] CSDN,凤舞飘伶,《Oracle 日志》: https://blog.csdn.net/woshaguayi/article/details/102697676

[5] CSDN,《深入浅出 Oracle:DBA 入门、进阶与诊断案例》读书笔记: https://blog.csdn.net/weixin_33910460/article/details/94703916

[6] Oracle Database 19c Performance Tuning Guide: https://docs.oracle.com/en/database/oracle/oracle-database/19/tgdba/

[7] Oracle Database 19c Backup and Recovery User’s Guide: https://docs.oracle.com/en/database/oracle/oracle-database/19/bradv/


相关文章