注册
Redo 与 LSN
专栏/技术分享/ 文章详情 /

Redo 与 LSN

🌌 2026/09/04 65 1 1
摘要

1. redo机制

INSERT、DELETE、UPDATE、CREATE TABLE 最终都是改物理数据页。Redo 记下页怎么变。故障重启或另一份库按产生顺序把记录作用到页上,文件内容就对齐。

临时表的修改不产生 Redo。所以临时表改动过不了另一份库;STANDBY 上仍允许改临时表,手册给的理由就是这一条,外加使用上灵活一点。

一条用户 SQL 在内部会拆成多个相互独立的 物理事务(Physical Transaction,PTX)。PTX 是一组改页动作:原子、有序、不能撤销。它和用户事务不是同一个东西。用户 COMMIT 往往对应多个 PTX。物理事务提交时,把产生的 Redo 写入当前日志包。

PTX 里是一条条 RREC(Redo Record),一条 RREC 对应一次改页动作。分两类。

物理 RREC:操作类型;页地址(表空间号、文件号、页号、页内偏移);页上改了什么。变长记录在记录头后面还有两字节长度。

逻辑 RREC:事务启动、提交、回滚、字典封锁、事务封锁、B 树封锁、字典淘汰等。手册写明这是为数据守护加的:备库重演和用户访问备库并发时要能对上锁;主库 DDL 后两边字典缓存不能各拿各的旧对象。备库解析到逻辑 RREC 时按内容生成事务、封锁对象、从字典缓存里淘汰过期对象。逻辑 RREC 一般在 PRIMARY 模式下才产生。

结构上可以记成:PTX 头 + 若干 RREC(每条有 type、len、数据;物理 RREC 还带 ADDR)。传输和刷盘不按单条 RREC 走,见下一节。


2. WAL、提交、检查点、启动重做

任何数据页从 Buffer 刷到磁盘之前,修改它的 Redo 必须已经写入联机日志文件。这是 WAL。COMMIT 保证的是本事务相关 Redo(含提交标记)落进联机日志;不保证脏页已经进 .dbf。掉电后靠有效 Redo 把页重做成提交后的样子。

检查点线程把 LSN ≤ CKPT_LSN 的那些页从 Buffer 写入数据文件,再推进 CKPT_LSN。推进之后,更早的联机日志占用的空间可以覆盖。检查点越勤,故障后要扫的 Redo 越短;越勤,数据页 I/O 越多。

启动过程里,联机日志重做完成后、回滚活动事务之前,状态是 After Redo。非 STANDBY 在进 OPEN 前也会经过这里。重做的起点不是文件头,是 0 号联机日志文件头里记下的 [CKPT_FILE, CKPT_OFFSET]

MOUNT 不生成 PWR 日志,数据页仍可刷盘,检查点仍推进。OPEN 切到 MOUNT 会强制回滚活动事务,不强制 Purge 已提交事务,不强制断连接,也不强制把 Buffer 脏页刷完。

SUSPEND 仍可访问对象、甚至改数据,但限制 Redo 刷盘。COMMIT 一类会触发刷盘的语句会挂住。切 SUSPEND 比重切 MOUNT 轻:不回滚活动事务。实时归档失败时 PRIMARY 会试图进 SUSPEND,避免再往联机日志里写一段对端没收到的包。


3. 日志包 RLOG_PKG

PTX 提交时 Redo 写入当前日志包。包是批量保存 PTX 的单元,大小不固定,固定包头加可变包头,里面连续放一个或多个 PTX。包头有类型、长度、包序号、LSN、产生日志的节点号、加密压缩、并行路数等。包能自描述。

刷盘和发到另一实例的最小单位是包,不是单条 RREC。触发刷盘:用户事务提交,或包写满。刷盘线程把包写入联机日志;配了归档时,刷盘线程还负责触发归档。发送方是刷盘线程,不是 dmwatcher

包序号生成时连续递增。相邻包的 LSN 总体递增;DSC 多节点下不一定连续。未开并行日志时,包内 LSN 递增。开了并行日志,一个包里有多路,每一路内部递增,路与路之间整体无序,所以是局部有序、整体无序。


4. 本地包序号和全局包序号

每个包有 LSEQGSEQ

LSEQ:节点内唯一、连续。校联机日志连续性。

GSEQ:守护组内主备共同维护,全局唯一、连续、递增。校归档连续性。判断归档是否连续、做备库归档恢复时,GSEQ 和 LSN 成对用,不要只盯一个。

写入联机日志前给包分配一个 GSEQ。每次把包写入联机日志后改 FILE_SEQ

名字 含义
CUR_SEQ / CSEQ 已分配的最大 GSEQ。主库:最新产生的包。备库:CSEQ 与自身 FSEQ 相同,不必等于主库 CSEQ
FILE_SEQ / FSEQ 已写入联机日志的最大 GSEQ。主库:本节点已落文件的最新包。备库:非控制节点此字段保持不变;控制节点跟与本控制节点号相同的那个主库节点的 FSEQ
APPLY_SEQ / ASEQ 备库已把主库原包写入自己联机日志时的原始 GSEQ。主库是 DSC 时,备库按主库每个节点各维护一套
RPKG_SEQ / RSEQ 备库已重演完的最大 GSEQ。主库是 DSC 时同样按节点各维护
SSEQ 备库明确可重演的最大 GSEQ
KSEQ 已收到、尚未确定能否重演的最大 GSEQ
SELECT CUR_LSN, FILE_LSN, FLUSH_LSN, CKPT_LSN, CUR_SEQ, FILE_SEQ FROM V$RLOG;

列名以现场 V$RLOG 为准。有的版本没有 FLUSH_LSN,有的还有 TOTAL_SPACE / FREE_SPACE。没有的列去掉再查。


5. LSN

LSN(Log Sequence Number)是系统维护的递增 Bigint,全局唯一地标识一次物理事务。物理事务提交时分配 CUR_LSN+1,再把 CUR_LSN 改成这个值。

动态视图主要是 V$RLOG。备库重演侧还有 V$RAPPLY_SYSV$RAPPLY_PARALLEL_INFOV$KEEP_RLOG_PKG

名字 含义
CUR_LSN / CLSN 已分配的最大 LSN。主库:最新产生。备库:准备重演刷盘的那个包上的最大 LSN,不是「这份库自己最新产生的用户修改」
FILE_LSN / FLSN 已写入联机日志文件。每写完一个包就更新
FLUSH_LSN 已发起刷盘、文件尚未写完
CKPT_LSN 检查点。LSN ≤ 此值的页已从 Buffer 进数据文件。检查点线程维护
APPLY_LSN / ALSN 备库已把主库原包写入自己联机日志。取自主库原包中的最大 LSN。主库是 DSC 时按节点各维护
RPKG_LSN / RLSN 备库已经重演完成的最大 LSN。主库是 DSC 时按节点各维护
SLSN 备库明确可重演的最大 LSN
KLSN 已收到、尚未确定能否重演。KEEP 包用这个

收到包不等于重演完。看同步看 ALSN、RLSN、SLSN,不能只看归档 Valid。

单机上能用这几个差判断本机日志路径:

  • CUR_LSN 往前跑、FILE_LSN 落后:包已分配,刷盘慢。COMMIT 会等刷盘。
  • FILE_LSN 往前跑、CKPT_LSN 落后:联机日志里有效段在变长,检查点慢或刷脏跟不上。可用空间变少时检查点会被逼着跑。
  • 有备库时:FILE_LSN 往前跑、RPKG_LSN 落后:对端 APPLY 慢。

监视器、守护日志里的 CLSN/FLSN/ALSN/RLSN/SLSN 要和 V$RLOGV$RAPPLY_SYS 对同一实例。数字对不上,先确认查的是哪一端:同一套缩写在主备上含义不同。


6. 联机日志文件

默认两个文件(如 DAMENG01.logDAMENG02.log),内部称 0 号、1 号。包顺序写入,写满切下一个。0 号是主文件,文件头有 CKPT_LSNCKPT_FILECKPT_OFFSETFILE_LSN 等。故障重启从 [CKPT_FILE, CKPT_OFFSET] 起读包、解析 RREC、改对应页。

检查点推进后,已刷进数据文件的那些 Redo 占用的空间可以覆盖。文件长度不随业务无限涨。这和本地归档相反:归档文件不能覆盖,直到删除或碰到空间上限。见 [[29_实时归档即时归档与KEEP]]。

三段:

  • 可用空间:可以覆盖写入
  • 有效 Redo:(CKPT_LSN, FILE_LSN],掉电必须重做
  • 待写入:(FILE_LSN, FLUSH_LSN],已请求刷盘尚未落文件

再往后到 CUR_LSN 的是已分配、还在包里未发起刷盘或未刷完的部分。

备库重演也会写自己的联机日志,但备库本地归档里是主库原包,所以备库联机日志和备库归档文件内容不完全一样。组内各节点归档文件要对齐的是「源库产生的那份包」。

查文件清单(路径、大小列名以版本为准):

SELECT * FROM V$RLOGFILE;

常见还能看到路径、大小。两个文件都在、大小接近配置值,是循环覆盖,不是「日志盘在涨」。归档目录在涨才是归档文件在堆积。


7. KEEP 和 LSN 怎么对上

实时路径:目标先把最新收到的包放在内存里,不立刻重演,这份叫 KEEP_RLOG_PKG。KLSN / KSEQ 标的就是「已收到、尚未确定能否重演」。SLSN / SSEQ 标的是「明确可重演」。即时路径先写源端联机日志再发,没有 KEEP。

手册用 LSN 把窗口钉死:源端在写入联机日志之前就发出去,对端已经拿到包。源端此时宕机,对端若立刻重演,源端重启后联机日志里没有这段,两边对不齐。KEEP 先压住;源端继续当主则丢 KEEP;对端接管则先重演 KEEP。接管时 APPLY_LSN ≥ 源端 FILE_LSN

KEEP 启动重演的三个时机:又来了一个新包(旧 KEEP 进 APPLY,新包当 KEEP);源端定时把 FILE_LSN 过来且等于本端 SLSN(说明 KEEP 对应的那段已经在源端落盘);切成新主之前。细节、ini、状态机见 [[29_实时归档即时归档与KEEP]]。

单机 NORMAL、只开本地归档时,不会出现 KEEP。V$KEEP_RLOG_PKG 空是正常的。


8. 和刷盘、检查点有关的参数

单机查的值:

SELECT PARA_NAME, PARA_VALUE FROM V$DM_INI WHERE PARA_NAME IN ( 'CKPT_INTERVAL', 'CKPT_RLOG_SIZE', 'CKPT_DIRTY_PAGES', 'CKPT_FLUSH_RATE', 'CKPT_FLUSH_PAGES', 'RLOG_BUF_SIZE', 'RLOG_POOL_SIZE', 'RLOG_SAFE_SPACE', 'BUFFER' ) ORDER BY PARA_NAME;

定时、日志占用、脏页数这几路检查点可以同时生效,某路为 0 表示关掉这一路。有效 Redo 把环写太满时,还会为了腾可用空间被逼着检查点。

手工检查点(权限足够时)。有的版本支持过程/函数,有的控制台命令和 DISQL 关键字不一样:

SELECT CHECKPOINT(100) FROM DUAL;

100 表示按该版本约定去刷一批脏页(常见是比例)。若报错,再试不带参数的 CHECKPOINT;,或查本版本管理员手册里检查点语句。不要假设 DISQL 一定认无参 CHECKPOINT;


9. 上机实验

不需要第二套实例,为了看见 PTX 提交后尺子怎么动、刷盘和检查点不是一回事、SUSPEND 卡的是刷盘、联机日志循环覆盖。

9.1 先看尺子和文件

SELECT STATUS$, MODE$ FROM V$INSTANCE; SELECT NAME, ARCH_MODE FROM V$DATABASE; SELECT CUR_LSN, FILE_LSN, CKPT_LSN FROM V$RLOG;

若本版本有这些列,一并看空间:

SELECT TOTAL_SPACE, FREE_SPACE FROM V$RLOG; SELECT * FROM V$RLOGFILE;

记下 CUR_LSNFILE_LSNCKPT_LSNCUR_LSNFILE_LSNCKPT_LSN 是常态。三者相等说明刚检查点过、没有未刷的包;不相等是正常负载,不是故障。

9.2 造 Redo,看 CUR / FILE 前进

CREATE TABLE LSN_SOLO(C1 INT, C2 VARCHAR(80)); INSERT INTO LSN_SOLO VALUES (1, 'one-row'); COMMIT; SELECT CUR_LSN, FILE_LSN, CKPT_LSN FROM V$RLOG;

一行提交后 CUR_LSNFILE_LSN 应比 9.1 大。CKPT_LSN 不必立刻跟上。

再批量造,让差距更容易看见:

BEGIN FOR I IN 1..20000 LOOP INSERT INTO LSN_SOLO VALUES (I, 'redo-gen'); IF MOD(I, 1000) = 0 THEN COMMIT; END IF; END LOOP; COMMIT; END; / SELECT CUR_LSN, FILE_LSN, CKPT_LSN FROM V$RLOG;

批量过程中反复查。CUR_LSN 先动、FILE_LSN 随后跟上。若 FILE_LSN 长期不追,查磁盘和日志相关等待,不要先去改归档。

DDL 也会进 Redo:

CREATE TABLE LSN_DDL(C1 INT); ALTER TABLE LSN_DDL ADD C2 INT; DROP TABLE LSN_DDL; COMMIT; SELECT CUR_LSN, FILE_LSN FROM V$RLOG;

9.3 检查点推进 CKPT_LSN

SELECT CUR_LSN AS C, FILE_LSN AS F, CKPT_LSN AS K FROM V$RLOG; SELECT CHECKPOINT(100) FROM DUAL; SELECT CUR_LSN AS C, FILE_LSN AS F, CKPT_LSN AS K FROM V$RLOG;

第二次 CKPT_LSN 应靠近当时的 FILE_LSN。有效区间 (CKPT_LSN, FILE_LSN] 变短。联机日志文件在操作系统里的字节数通常不变。

若函数报错,不要循环重试同一句。换本版本的检查点语法,或等 CKPT_INTERVAL 到点后再查一次 CKPT_LSN

9.4 SUSPEND 卡住的是刷盘

单机 NORMAL 可做。看清效果立刻 OPEN,不要挂在业务会话上。

先记下 FILE_LSN

ALTER DATABASE SUSPEND; SELECT STATUS$ FROM V$INSTANCE; SELECT CUR_LSN, FILE_LSN, CKPT_LSN FROM V$RLOG;

再在同一会话:

INSERT INTO LSN_SOLO VALUES (-1, 'suspend-test'); COMMIT;

COMMIT 会挂住。另开会话查 FILE_LSN,应停在 SUSPEND 之前。这就是手册说的「限制 Redo 刷盘」。

恢复:

ALTER DATABASE OPEN; SELECT STATUS$ FROM V$INSTANCE; SELECT CUR_LSN, FILE_LSN, CKPT_LSN FROM V$RLOG; SELECT COUNT(*) FROM LSN_SOLO WHERE C1 = -1;

OPEN 之后挂住的 COMMIT 才能结束,FILE_LSN 前进,那一行可见。PRIMARY/STANDBY 下 OPEN 要加 FORCE。单机 NORMAL 一般不用。没有第二实例时不要 ALTER DATABASE STANDBY

MOUNT 和 SUSPEND 不能直接互切。要经过 OPEN。

9.5 联机日志在盘上的样子

数据目录按自己的装法改。下面假设 /dmdata/DAMENG

ls -l /dmdata/DAMENG/*.log

应能看到 DAMENG01.logDAMENG02.log(库名不同则前缀不同)。大小是初始化或后来扩出来的固定长度量级,造完 9.2 的 Redo 再 ls,这两个文件通常还是那么大。归档目录 /dmarch/DAMENG 若已打开,那里的 ARCHIVE_LOCAL1_*.log 才会随日志量增加。

9.6 有第二实例以后对哪几列(先记着,现在不必连)

备库:

SELECT * FROM V$RAPPLY_SYS; SELECT * FROM V$RAPPLY_PARALLEL_INFO; SELECT * FROM V$KEEP_RLOG_PKG;

APPLY / RPKG 应跟上源端已落盘的部分。KEEP 非空时 KLSN 可能大于已重演的 RLSN。即时目标上 KEEP 应一直空。现在没有 STANDBY 不要为了跑这三条去改角色。

上机截图:
lsn3001rlog.png
lsn3002redo.png
lsn3003ckpt.png
lsn3004suspend.png
lsn3005open.png
lsn3006logfiles.png

10. 读数时容易弄错的情况

同一套名字,主库 CLSN 是最新产生的物理事务;备库 CLSN 是准备重演刷盘的包。拿备库 CLSN 当「业务已经写到哪」会看错。

FILE_LSN 是本实例联机日志。备库的 FILE_LSN 含重演时自己新写的 Redo,和归档文件里的主库原包不是同一份内容。对齐归档用 GSEQ + 原包 LSN(ALSN),不要拿两端 FILE_LSN 直接减。

并行日志开着时,不要假设一个包内 LSN 全程递增。

CKPT_LSN 落后不是归档失败。先看检查点和脏页。归档 Valid 也不是 APPLY 完成。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服