Oracle 数据库恢复操作最关键的依据就是 redo log
,它记录了对数据库所有的更改操作。在研究如何提取 redolog 中 DML 操作的过程可谓一波三折,因为介绍 redolog 结构细节的资料实在太少了,不过好在最后大致理清了它的结构,并开发了一个基于日志的同步软件。
本系列文章就记录下研究过程中遇到的问题和使用的分析命令、工具。
1. 什么是 Redo Log
Redo Log 就是一组文件,它们记录了对数据库的所有操作,主要包括:
- 所有
DML
操作,INSERTUPDATEDELETESELECT FOR UPDATE
- 所有
DDL
操作,CREATE TABLEALTER 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 采用循环覆盖的方式写入:当一个文件写满后,开始写入下一个,当最后一个文件写满后,返回第一个开始写入,如此循环。
这样写入会导致数据丢失吗?默认情况下会。
数据库日志有两种模式:归档
和 非归档
,非归档模式就会有覆盖写入的问题。在归档模式下,当一组 ReodLog 文件写满,发生切换时,Oracle 会保证归档完成前此组文件不被覆盖。
可以使用以下命令手动触发日志切换:
SQL> alter system switch logfile
3. Redo Log 基本结构
Redo Log 是由一系列的 redo record
组成,每个 redo record
又是由一组 change vector
组成,每个 change vector
都记录了对单个数据块的更改操作。
Redo Log 文件在存储结构上,是按块存储的,默认情况下块大小是磁盘扇区的大小,通常是 512 字节
,它的格式取决于操作系统和数据库版本,这里的分析都是在 Windows Server 2008 R2
和 Oracle 11g
的基础上进行的。
Redo Log 是按顺序写入的,基本格式如下:
前两个块记录的是元信息,分别是:
- 第1个块记录文件本身的信息,比如文件类型,块大小和块数,这部分称为
File Header
- 文件头 - 第2个块记录数据库实例相关信息,比如数据库SID,数据库版本,这部分称为
Redo Log Header
- 重做日志头
结合日志写入方式,从整体上看,读取这一组 Redo Log 文件,在内存中可以把它们看成按块为存储单元的环形缓冲区来处理,解析的过程就是读取一个个 Record
。
3.1 Redo Record
一个 Redo Record 可能占用一个 block,也可能占用多个 block,也可能只占用 block 的一部分,这取决于它的长度,长度字段就存储在 Record Header
头部,结构如下:
值得注意的是,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
都代表一个数据库操作,比如增删改,事务开始,事务回滚,事务提交等等,它的格式如下:
其中:
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 Code
和Sub Code
,比如11.2
下图是一些常用的操作
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
,它表示此 Change
在 undo block
中的地址,长度是 7字节
,也由三部分组成:
- undo block 的 DBA (0x00c007a0)
- 序号 (0x009b)
- 在 block 中的 Record 编号 (0x41)
下图是一个完整事务的示例:
执行了两个 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:databaseoracleadministrat
ordiag
dbmsorclorcl race
第二,在执行 dump 命令前后使用以下命令,可以显示:
SQL> oradebug setmypid;
SQL> alter system dump logfile xxxxxxx
SQL> oradebug tracefile_name;
c:databaseoraclexxxxxxxxxxxxxx.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
按照一定的运算逻辑会转成 50
,61
就是字符 a
的 ASCII 编码。
5. 总结
建议上面的命令都手动执行下,别人总结的终究没有自己经历下,来的印象深刻。
本系列文章主要参考的有:
- Julian Dyke 对 RedoLog 分析的 PPT
- David Litchfield 对 Redo Logs 二进制文件剖析的 PDF
- zhoubihui 发布在 GitHub 上的 redo_log_calculate_analysis 研究文章