Oracle 数据库恢复操作最关键的依据就是 redo log,它记录了对数据库所有的更改操作。在研究如何提取 redolog 中 DML 操作的过程可谓一波三折,因为介绍 redolog 结构细节的资料实在太少了,不过好在最后大致理清了它的结构,并开发了一个基于日志的同步软件。

本系列文章就记录下研究过程中遇到的问题和使用的分析命令、工具。

1. 什么是 Redo Log

Redo Log 就是一组文件,它们记录了对数据库的所有操作,主要包括:

  • 所有 DML 操作,INSERT\UPDATE\DELETE\SELECT FOR UPDATE
  • 所有 DDL 操作,CREATE TABLE\ALTER TABLE
  • 所有因 Recursive SQL 引起的变化,比如执行 DDL 语句时,Oracle 会隐式的执行其他 SQL 修改数据字典

在数据库事务 COMMIT 之前,Oracle 既会把变化信息写入 Rodo Log 文件,也会把原始数据-即 UNDO Segments 写入。因此,Redo Log 不仅用于恢复数据,还能保护数据回滚。

2. Oracle 如何写入 Redo Log

Oracle 数据库每个实例都有一个 redo thread 负责日志的写入,称为 LGWR,LoG WRiter 的简写。Redo Log 文件至少有两个,LGWR 采用循环覆盖的方式写入:当一个文件写满后,开始写入下一个,当最后一个文件写满后,返回第一个开始写入,如此循环。

Oracle RedoLog-基本概念和组成-LMLPHP

这样写入会导致数据丢失吗?默认情况下

数据库日志有两种模式:归档非归档,非归档模式就会有覆盖写入的问题。在归档模式下,当一组 ReodLog 文件写满,发生切换时,Oracle 会保证归档完成前此组文件不被覆盖。

Oracle RedoLog-基本概念和组成-LMLPHP

可以使用以下命令手动触发日志切换:

SQL> alter system switch logfile

3. Redo Log 基本结构

Redo Log 是由一系列的 redo record 组成,每个 redo record 又是由一组 change vector 组成,每个 change vector 都记录了对单个数据块的更改操作。

Redo Log 文件在存储结构上,是按存储的,默认情况下块大小是磁盘扇区的大小,通常是 512 字节,它的格式取决于操作系统和数据库版本,这里的分析都是在 Windows Server 2008 R2Oracle 11g 的基础上进行的。

Redo Log 是按顺序写入的,基本格式如下:

Oracle RedoLog-基本概念和组成-LMLPHP

前两个块记录的是元信息,分别是:

  • 第1个块记录文件本身的信息,比如文件类型,块大小和块数,这部分称为 File Header - 文件头
  • 第2个块记录数据库实例相关信息,比如数据库SID,数据库版本,这部分称为 Redo Log Header - 重做日志头

结合日志写入方式,从整体上看,读取这一组 Redo Log 文件,在内存中可以把它们看成按块为存储单元的环形缓冲区来处理,解析的过程就是读取一个个 Record

3.1 Redo Record

一个 Redo Record 可能占用一个 block,也可能占用多个 block,也可能只占用 block 的一部分,这取决于它的长度,长度字段就存储在 Record Header 头部,结构如下:

Oracle RedoLog-基本概念和组成-LMLPHP

值得注意的是,Record 的长度是动态的,计算方法以及二进制文件字节分析后续文章会详细介绍,这里简单看下使用 system dump 命令导出的头信息:

REDO RECORD - Thread:1 RBA: 0x000009.0000029e.0010 LEN: 0x02ac VLD: 0x0d
SCN: 0x0000.0010c5e6 SUBSCN:  1 01/02/2021 21:09:41

其中主要字段的含义:

  • RBA: Redo Byte Address, 由三部分组成:日志序号(0x9),块编号(0x29e),块中字节偏移量(0x10)
  • LEN: Record 长度,包含头部长度
  • VLD: 头部长度标识,按照一定的逻辑计算头的长度,0x0d 就表示头部长度为 68 字节
  • SCN: System Change Number,也可称为 System Commit Number。当一个事务提交时,LOWR 将缓冲区内容写入文件,并为每个已提交的事务,分配一个标识,就是 SCN。也就是说,可以通过 SCN 跟踪数据库变化,也可以根据它决定从哪开始恢复数据。

此外,借助 SCN 还可以有针对的 dump 运行的 DML 语句,本文最后有相关的命令。

3.2 Change Vector

Record 头后面就跟着,一个或多个 Change Vector,每个 Change 都代表一个数据库操作,比如增删改,事务开始,事务回滚,事务提交等等,它的格式如下:

Oracle RedoLog-基本概念和组成-LMLPHP

其中:

  • Change Header 固定 24 字节 长度
  • Length Vector 表示后面有多少个 Change Record,每 2 字节 表示一个长度,计算长度时,需要进行 4 字节对齐
  • Change Record 就是具体的变化内容了,不同的操作有不同的格式。

使用 system dump 看下 Change Header 的信息:

CHANGE #1 TYP:0 CLS:1 AFN:4 DBA:0x01000085 OBJ:73194 SCN:0x0000.000e606a SEQ:1 OP:11.2 ENC:0 RBL:0

其中主要字段的含义:

  • TYP: Change Type
  • CLS: Class 等于 X$BH.CLASS 暂时不知用途
  • DBA: Database Block Address,4字节长度,高10位表示相对文件号,低22位表示块号
  • OP: 操作码,区分操作类型,每个操作码都由两部分组成:Layer CodeSub Code,比如 11.2

下图是一些常用的操作

Oracle RedoLog-基本概念和组成-LMLPHP

3.3 Transactions 事务

开始执行一个 DML 操作时,会创建一个 OP:5.2 的 Change,标识事务开始:

CHANGE #2 TYP:0 CLS:19 AFN:3 DBA:0x00c00090 OBJ:4294967295 SCN:0x0000.0010c5bb SEQ:3 OP:5.2 ENC:0 RBL:0
ktudh redo: slt: 0x0018 sqn: 0x0000033a flg: 0x0012 siz: 108 fbi: 0
            uba: 0x00c007a0.009b.40    pxid:  0x0000.000.00000000

事务提交或者回滚时,会创建一个 OP:5.4 的 Change,标识事务结束:

CHANGE #4 TYP:0 CLS:19 AFN:3 DBA:0x00c00090 OBJ:4294967295 SCN:0x0000.0010c5e6 SEQ:1 OP:5.4 ENC:0 RBL:0
ktucm redo: slt: 0x0018 sqn: 0x0000033a srt: 0 sta: 9 flg: 0x2 ktucf redo: uba: 0x00c007a0.009b.41 ext: 2 spc: 640 fbi: 0

一个完整的事务都有一个唯一标识,日志中的体现就是 XID

xid:  0x0002.018.0000033a

XID 长度是 8 字节,由三部分组成:

  • USN: Undo segment number (0x0002),目前不知如何获取此值
  • slt: Undo segment header transaction table slot (0x018),对应 ktudh/ktucm 中的 slt
  • sqn: 0x0000033a,对应 ktudh/ktucm 中的 sqn

ktudh/ktucm 中有一个 uba 字段,内容是 uba: 0x00c007a0.009b.41,它表示此 Changeundo block 中的地址,长度是 7字节,也由三部分组成:

  • undo block 的 DBA (0x00c007a0)
  • 序号 (0x009b)
  • 在 block 中的 Record 编号 (0x41)

下图是一个完整事务的示例:

Oracle RedoLog-基本概念和组成-LMLPHP

执行了两个 update,其中 c1=1 的 c2 原先等于 100,更新成了 101;c1=2 的 c2 原先等于 200,更新成了 201;

4. 相关命令

4.1 日志归档和非归档

查询数据库当前的日志模式:

SQL> archive log list;
or
SQL> select log_mode from v$database;

查看在线日志:

SQL> select l.STATUS, lf.MEMBER from v$log l, v$logfile lf where l.GROUP# = lf.GROUP#;

查看已归档日志:

SQL> select recid, stamp, thread#, sequence#, name from v$archived_log;

查看默认归档路径

SQL> show parameter db_recovery_file_dest;

日志开启归档模式:

SQL> shutdown immediate;
SQL> startup mount
SQL> alter database archivelog;
SQL> alter database open;
SQL> archive log list;

日志关闭归档模式:

SQL> shutdown immediate;
SQL> startup mount
SQL> alter database noarchivelog;
SQL> alter database open;

4.2 Redo Log Dump

使用 ALTER SYSTEM 命令可以把二进制的 Redo Log 文件转储为任何文本编辑器可读的 ASCII 编码文件,有助于我们理解分析二进制结构,该命令的语法如下:

alter system dump logfile 'FileName'
  scn min MinimumSCN
  scn max MaximumSCN
  time min MinimumTime (s)
  time max MaximumTime (s)
  layer Layer
  opcode Opcode
  dba min FileNumber BlockNumber
  dba max FileNumber BlockNumber
  rba min LogFileSequenceNumber BlockNumber
  rba max LogFileSequenceNumber BlockNumber
  objno ObjectNumber
  xid UndoSegmentNumber UndoSlotNumber UndoSequenceNumber;

使用 SCN

SQL> alter system dump logfile '/u01/app/oradata/orcl/redo03.log' scn min 1099234 scn max 1099246;

使用 RBA

SQL> select cpodr_seq,cpodr_bno from x$kcccp where rownum=1;
 CPODR_SEQ  CPODR_BNO
---------- ----------
         9       1514
SQL> DML (insert/update/delete)
SQL> select cpodr_seq,cpodr_bno from x$kcccp where rownum=1;
 CPODR_SEQ  CPODR_BNO
---------- ----------
         9       1518

SQL> alter system dump logfile '/u01/app/oradata/orcl/redo03.log' rba min 9 1514 rba max 9 1518;

注意:每次 dump 后都需要退出此次会话,重新登录后再 dump,否则结果只会存储到一个文件内。

4.3 查询 dump 路径

有两种办法查询 dump 路径。

第一,使用以下命令查看默认路径:

SQL> show parameter user_dump_dest;
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
user_dump_dest                       string      c:\database\oracle\administrat
                                                 or\diag\rdbms\orcl\orcl\trace

第二,在执行 dump 命令前后使用以下命令,可以显示:

SQL> oradebug setmypid;
SQL> alter system dump logfile xxxxxxx
SQL> oradebug tracefile_name;
c:\database\oracle\xxxxxxx\xxxxxxx.trc

4.4 dump 一个 insert 操作

首先,查看当前使用的在线日志是哪一个,即状态为 CURRENT 的文件:

SQL> select l.STATUS, lf.MEMBER from v$log l, v$logfile lf where l.GROUP# = lf.GROUP#;

然后,往 scott 用户的 dept 表插入一条数据,查看 dump 的结果,命令如下:

SQL> select current_scn from v$database;
CURRENT_SCN
-----------
    1099234
SQL> insert into scott.dept values(50, 'a', 'a');
SQL> commit;
SQL> select current_scn from v$database;
CURRENT_SCN
-----------
    1099246
SQL> alter system dump logfile '/u01/app/oradata/orcl/redo03.log' scn min 1099234 scn max 1099246;

最后,二进制 Redo log dump 的结果,这里只摘出了 insert 部分,太长了,相信也没人看~~:

CHANGE #1 TYP:0 CLS:1 AFN:4 DBA:0x01000085 OBJ:73194 SCN:0x0000.000e606a SEQ:1 OP:11.2 ENC:0 RBL:0
KTB Redo
op: 0x01  ver: 0x01
compat bit: 4 (post-11) padding: 0
op: F  xid:  0x0002.018.0000033a    uba: 0x00c007a0.009b.40
KDO Op code: IRP row dependencies Disabled
  xtype: XA flags: 0x00000000  bdba: 0x01000085  hdba: 0x01000082
itli: 1  ispac: 0  maxfr: 4858
tabn: 0 slot: 0(0x0) size/delt: 10
fb: --H-FL-- lb: 0x1  cc: 3
null: ---
col  0: [ 2]  c1 33
col  1: [ 1]  61
col  2: [ 1]  61

简单解释下,OP:11.2 表示这是一个 insert 操作;OBJ:73194 表示操作的表是 scott.dept;最后三行的 col 表示操作的字段数据,显示的数值都是 16 进制,其中 c1 33 按照一定的运算逻辑会转成 5061 就是字符 a 的 ASCII 编码。

5. 总结

建议上面的命令都手动执行下,别人总结的终究没有自己经历下,来的印象深刻。

本系列文章主要参考的有:

  • Julian Dyke 对 RedoLog 分析的 PPT
  • David Litchfield 对 Redo Logs 二进制文件剖析的 PDF
  • zhoubihui 发布在 GitHub 上的 redo_log_calculate_analysis 研究文章

以上资料网上均能搜索到,当然了,您也可以关注下wx公众号,「小创编程」回复关键字「redolog」获取。

01-04 21:20