☰
Oracle SCN与检查点:数据库恢复效率的源头与实操
2026/10/2 14:37:16 网站建设 项目流程

简介:面向Oracle DBA、数据库运维人员及备考者的原理性文档,系统讲解SCN(系统改变号)与检查点两大核心机制。内容从SCN定义出发,阐明它作为Oracle内部逻辑时钟如何标识事务提交版本,解释SCN在事务提交或回滚时变化、不会重置为零等特性,并说明SCN广泛存在于事务表、控制文件、数据文件头、日志文件及数据块头等位置;随后提供通过dbms_flashback.get_system_change_number获取当前SCN的SQL示例,并转入检查点机制,说明其目标是减少崩溃恢复时间,涉及脏数据写入、DBWR与CKPT进程配合、更新数据文件头和控制文件等关键步骤,以及通过v$datafile查询文件检查点SCN的方法。资源为1个PDF文档,大小约81KB,内容紧凑、附带查询示例,可帮助读者快速掌握概念与视图含义,理清SCN和检查点在崩溃恢复中的协作关系。目前已有434人学习浏览,适合Oracle进阶学习者巩固数据库核心机制。

1. 我为什么把 SCN 和检查点放一起讲:它俩才是数据库恢复效率的源头

不少刚接触 Oracle 的同行都有同感:SCN 和检查点这两个词,在官方文档里被解释得很抽象——SCN 是系统改变号,检查点是个数据库事件,看完仍是“名词认得,落地不会”。我第一次被拉去处理一个“重启数据库耗时异常久”的现场时,花了一整天,才把 SCN、检查点、重做日志这三件事串起来。那份《Oracle SCN与检查点详解》文档给我省了不少时间,所以这篇笔记就按“它是什么、在哪能看到、怎么验证、踩过哪些坑”的顺序,把这份资料里的核心内容重新打成一份能照着操作的版本。

一句话先说清关系:SCN 是 Oracle 内部的逻辑时钟,检查点是拿这个时钟作刻度,把脏数据写回磁盘并同时缩短崩溃恢复时重做日志重放范围的手段。整篇不展开分布式事务和 RAC 内部细节,面向 oracle 入门阶段最头疼的恢复时间问题,也覆盖面试里常问的v$datafile查看检查点 SCN 这类硬核题。

2. 先从 SCN 的定义入手:为什么它叫逻辑时钟而不是提交号

2.1 不必纠结 Change 还是 Commit:记住三件事就行

文档开头就用了一整段来解释System Change Number和System Commit Number的命名争议。老实说,这两个词在官方文档里都出现过,考证哪个是原意没有太大意义。你需要记住的是三件事实:SCN 是数据库全局唯一的递增编号,SCN 用来标识某个确切时刻的数据库版本,SCN 在事务提交或回滚时会被分配并作为事务排序依据。

这个“排序”作用比字面意思重要得多。Oracle 做一致性读时,需要判断某个数据块版本对当前查询是否可见,比较的就是块头记录的 SCN 和查询启动时对应的 SCN。事务表、控制文件、数据文件头、日志文件头、数据块头里都记录着不同含义的 SCN 值,它们都是同一个全局时钟在不同物理位置的快照。

还有一点容易忽略:SCN 的值随时间增加,但并不是连贯的。两次查询之间可能一次涨几百,完全正常。原因也很朴素——Oracle 向全局生成器批量申请一段 SCN 范围,避免每次都做全局串行分配。后面排查时看到 SCN 跳变,先别慌。

2.2 获取 SCN 的正确姿势:函数调用别少了括号

文档给出的方式是调用DBMS_FLASHBACK包里的函数,原脚本如下:

-- 获取系统当前的近似 SCN SELECT dbms_flashback.get_system_change_number FROM dual;

这段在 SQL*Plus、PL/SQL Developer、SQL Developer 里都能执行,输出通常是一个很大的数字,例如文档现场拿到的是6051905241299。这个数字本身没有跨库可比性,你只需要关注它在本库内随时间的变化趋势。

有一点必须提醒:GET_SYSTEM_CHANGE_NUMBER是一个无参函数,在 SQL 的 select 列表里可以省略括号,但你要是把它写进 PL/SQL 赋值语句,就必须补上空括号。常见翻车写法是:

-- 在存储过程或匿名块中必须加括号,否则编译报错 DECLARE l_scn NUMBER; BEGIN l_scn := dbms_flashback.get_system_change_number(); -- 这里要括号 dbms_output.put_line(l_scn); END; /

我的习惯是查完之后再用v$database交叉验证一次,两个来源应该非常接近:

-- 从控制文件角度读取当前 SCN SELECT current_scn FROM v$database;

注意,v$database.current_scn在 10g 之后才普遍可用,较早的库还是以DBMS_FLASHBACK为主。交叉验证的意义在于:如果两个值差异过大,说明你查询的会话可能正巧跨过一个 redo 生成峰值,或者控制文件与内存视图存在同步延迟,需要结合 alert log 再看。

2.3 SCN 在库里无处不在:各个“SCN 前缀名”分别对应哪个物理位置

文档列出的几个位置很典型:事务表、控制文件、数据文件头、日志文件、数据块头。这里我用一张表把它们对应起来,方便你以后看文档时不会被各种前缀绕晕:

物理位置常见名称记录内容与作用
数据文件头Checkpoint SCN、Stop SCN、Checkpoint Cnt最近一次检查点完成时的 SCN,崩溃恢复时从这里开始找 redo
控制文件Checkpoint SCN、Resetlogs SCN全库检查点进度、日志历史、resetlogs 之后的序号记录
日志文件头First Change#、Next Change#该日志内第一条和最后一条 redo 对应的 SCN 边界
redo 记录Change Vector 中的 SCN逐块记录变更时点的 SCN,用于前滚时判断是否需要应用
数据块头Block Cleanout SCN事务提交后延迟清理时标记块版本
事务表事务 SCN提交时分配的唯一事务编号

这块最容易犯的错是拿不同物理位置的 SCN 直接做大小比较。比如数据文件头的 Checkpoint SCN 和 redo 日志里的 First Change# 本来就不该相等,一个表示“已写盘的进度”,另一个表示“日志起点”,比较它们没有业务意义。真正有意义的比较,是当前 SCN 和检查点 SCN 之间的跨度,这个跨度才决定恢复时要重放多少 redo。

3. 检查点本质是为了缩短崩溃恢复:从重做日志重放说起

3.1 为什么提交时不立刻写盘:redo 的存在是前提

很多文档把检查点讲得复杂,其实核心就一句话:它是一个数据库事件,存在的根本意义是减少崩溃恢复时间。

理解这件事要先接受一个设计前提:Oracle 不会在每次事务提交时把修改的数据块写回数据文件,那样写 I/O 会高到无法接受。事务提交时真正必须落盘的是重做日志,因为 redo 记录的是“怎么把这次修改重演出来”,日志一旦持久化,事务就安全了。数据块可以先留在 Buffer Cache 里,等后续检查点或空间压力再把脏块刷出。

断电崩溃时,Buffer Cache 里那些已提交但没写进数据文件的修改会丢,Oracle 启动后会做前滚,也就是把崩溃前那一段 redo 再应用一遍,把数据块恢复成崩溃前的状态,然后回滚未提交事务。这个过程叫崩溃恢复,大家最关心的就是它要多久。

耗时取决于什么?取决于要读多少 redo。如果能确定一个较早的“安全起点”,告诉前滚引擎在这个点之前的数据块都已经落盘了,恢复时就可以只从这个点开始往后应用 redo。这个点就是检查点。没有检查点作为锚点,崩溃恢复就得从开天辟地开始重放,显然不现实。

3.2 检查点发生时的一条完整链路:DBWR 与 CKPT 各自干什么

文档把过程描述得很清楚:检查点发生时,Oracle 会通知 DBWR 进程,把这个检查点 SCN 之前的脏数据从 Buffer Cache 写回磁盘;写完之后,CKPT 进程更新控制文件和数据文件头,把检查点相关信息记录下来。

注意两个进程分工不同。DBWR 负责搬运脏块,它关注的是“哪些块要写”;CKPT 负责记账,它更新的是“写到哪个 SCN 了”。这也是为什么你查看v$datafile里的CHECKPOINT_CHANGE#时,看到的是 CKPT 进程记录的值,而不是 DBWR 实时刷盘的值。

检查点完成之后,一个重要变化是:检查点之前对应的 redo 记录对崩溃恢复不再有用,日志文件里的这些位置可以被覆盖复用。所以检查点频率越高,崩溃恢复要应用的 redo 越少,恢复时间越短。

反过来,频繁检查点会带来额外的写 I/O,因为每次检查点都会触发大量脏块写入,尤其是在更新密集的库上,代价不可忽视。文档里那句话很到位:数据库内部操作相关性极强,优化是系统工程,不能草率。

3.3 先跑一遍 v$datafile:数据文件头的 Checkpoint SCN 是怎么一步步更新的

拿到这份文档后,我第一次动手验证的就是下面这条 SQL。建议你也亲自跑一次,因为看一百遍概念不如看一眼真实数据:

-- 查看每个数据文件当前的检查点 SCN 与时间 SELECT file#, NAME, CHECKPOINT_CHANGE#, to_char(CHECKPOINT_TIME, 'yyyy-mm-dd hh24:mi:ss') AS cpt FROM v$datafile ORDER BY file#;

文档现场的简化输出如下:

FILE# NAME CHECKPOINT_CHANGE# CPT ----- -------------------- ---------------------- -------------------- 1 /u01/.../system01.dbf 6051905239995 2016-05-05 04:14:32 2 /u01/.../sysaux01.dbf 6051905239995 2016-05-05 04:14:32 3 /u01/.../undotbs01.dbf 6051905239995 2016-05-05 04:14:32 ... 8 rows selected

这里有个观察点值得展开:输出里所有文件头的CHECKPOINT_CHANGE#完全一致,是因为现场刚刚经历过一次全库检查点。在正常运行的库上,不同文件的检查点 SCN 经常不一致,尤其是加了数据文件或做过离线备份之后,个别文件头会落后于其他文件。这本身不是异常,判断它是否健康要看“落后多少”以及“是否一直落后”。

CHECKPOINT_TIME表示该文件最近一次完成检查点的时间。很多人问怎么确认检查点是否真的发生过,最简单就是隔几分钟再查一次,看这两个字段有没有往前推。注意数据文件头的检查点 SCN 是逐步推的,不是一个 session 里执行一条命令就能立刻跳到最后,DBWR 写脏、CKPT 记账都需要时间。

4. 把检查点状态查透:视图、参数与差值计算

4.1 四个视图串起来:日志离检查点有多远

单纯看v$datafile只能知道“检查点走到哪了”,要判断“离恢复目标还有多远”,还得把当前 SCN 和 redo 日志的边界信息拉进来。我一般会把下面几条 SQL 作为一套组合拳来跑。

-- 组合查询:当前 SCN 与各数据文件检查点 SCN 的差距 SELECT df.file#, df.name, df.checkpoint_change# AS file_cp_scn, (SELECT current_scn FROM v$database) AS db_current_scn, (SELECT current_scn FROM v$database) - df.checkpoint_change# AS redo_span, df.checkpoint_time FROM v$datafile df ORDER BY redo_span DESC;

这个redo_span就是我前面说的“跨度”,它表示从上一次检查点确认写盘之后,数据库又产生了多少新的变化。恢复时至少要重放这些对应的 redo。跨度越大,前滚时间越长。

这只是逻辑判断,不是性能指标。跨度大不等于一定会慢,还得看 redo 量本身有多大。比如同样跨度 100 万 SCN,事务量小的库对应的 redo 可能只有几百 MB,事务量大的库可能就是几个 GB。所以接下来要看另一个东西——日志文件的边界。

4.2 把恢复需要的日志范围画出来:日志文件的 first/next change

redo 日志按 thread 和 sequence 组织,每份日志文件都有明确的 SCN 边界。查看这些边界的标准查询是:

-- 查看当前所有 redo 日志的 SCN 边界与状态 SELECT thread#, sequence#, first_change#, next_change#, status FROM v$log ORDER BY thread#, sequence#;

字段含义不复杂:first_change#是这份日志里第一条 redo 的 SCN,next_change#是下一条 redo 的 SCN。STATUS=CURRENT表示正在写,ACTIVE表示日志已写完但内容对崩溃恢复还有用,INACTIVE表示对应的脏块已经完成检查点,日志可以复用。

这就是检查点和日志切换的协同关系:检查点推进到某个 SCN 之后,那些next_change#小于该 SCN 的日志就不再需要保留,状态会从 ACTIVE 变 INACTIVE。所以当你看到大量 ACTIVE 日志积压时,不用急着怀疑 redo 有问题,先回去看v$datafile的检查点 SCN 是否卡住了,多半是脏块刷盘跟不上。

增量检查点的概念也在这里体现:Oracle 不会等所有脏块都写完才更新检查点位置,而是让检查点 SCN 像一个游标一样顺着 redo 流逐步推进。只要 DBWR 写脏持续推进,检查点就会慢慢追着 CURRENT 日志跑;如果检查点长期停在某个 SCN 不动,前滚范围就会拉长。

4.3 参数面:fast_start_mttr_target 与 log_checkpoint_timeout 的取舍

拜访维护过 Oracle 的同行,几乎都会被提醒这两个参数。它们都属于“检查点触发策略”的调节开关,定位完全不同。

fast_start_mttr_target的单位是秒,表示你期望的崩溃恢复目标时间。Oracle 会在后台估算:要在这个时间内完成恢复,检查点得分推到哪里、脏块要提前写多少。它是目标导向,有点“自己看着办”的意思。

log_checkpoint_timeout的单位也是秒,表示最多隔多久必须触发一次检查点。它是时间导向,不管事务量大小,到点就触发。

查看当前值的标准写法:

-- 查看检查点相关参数当前值 SELECT name, value, isdefault, description FROM v$parameter WHERE name IN ('fast_start_mttr_target', 'log_checkpoint_timeout') ORDER BY name;

实测中,很多库两个参数都没显式设置,走的是默认值和自动调整。需要调整时我的习惯是:先在业务低谷把fast_start_mttr_target从默认值改到 300 秒左右,观察一段时间,不要上来就改到 60 秒。恢复时间和性能是邻居关系,你压缩恢复时间,脏块就更频繁落盘,I/O 压力就上升。

下面这条命令可以用来做临时调整,重启后失效,适合用来验证影响:

-- 临时设置 MTTR 目标为 300 秒,仅当前实例生效 ALTER SYSTEM SET fast_start_mttr_target = 300 SCOPE = MEMORY;

验证期建议配合v$mttr_target_advice观察预估恢复时间。老版本里这个顾问视图比手工估算靠谱得多,能看到不同目标值下的预估恢复时长和额外写 I/O 量。调整参数不是拍脑袋,先看顾问数据再动手。

提示:新手常见的迷惑点在于把fast_start_mttr_target当成性能开关,以为调小就能让系统变快。它换来的是恢复时间缩短,代价是刷脏频率上升,慢查询和 I/O 等待可能跟着变多。只有当你确知“恢复时间长”是痛点时,才有必要调它。

5. 避坑清单:SCN 与检查点实际踩过的五个现场

5.1 打开数据库慢到怀疑人生

现象:一台测试库断电后启动,前滚阶段跑了 40 分钟,业务方不停催。

原因:检查v$datafile时发现,数据文件头的CHECKPOINT_CHANGE#停留在好几个小时之前的 SCN,而这几个小时内日志文件疯狂切换,导致需要重放的 redo 横跨几十份归档日志。说明库在崩溃前检查点推进已经明显滞后。

解决:等库正常打开后,先查redo_span确认滞后量,再把fast_start_mttr_target下调到一个可控范围,比如 300 秒;同时确认归档目录空间充足,避免下次崩溃后日志早被清理。从那以后我遇到慢启动,第一反应不再是怀疑磁盘性能,而是先看检查点距离。

5.2 没跑大事务,SCN 却一直在涨

现象:有人反馈数据库 SCN“异常增长”,两次查询之间跳了几十万,怀疑出了 bug,甚至想通过重置数据库来“归零”。

原因:SCN 本来就不是按提交次数逐个分配的,Oracle 会批量预分配一段编号,减少全局争用;而且大量日志切换、内部递归操作也会消耗 SCN。文档里写得很明白,除非重建数据库,SCN 永远不会被重置为 0。数值大不是问题,涨得快才要关注它背后的 redo 生成量。

解决:不用做任何处置。只需要连续采样一段时间,确认 SCN 增长和日志生成量在同一节奏;如果哪一天 SCN 暴涨但日志量平稳,再回过头查是否存在反复 resetlogs 或时钟跳变类的外部因素。

5.3 数据文件头和控制文件对不上

现象:同一时间查v$datafile和v$datafile_header,发现CHECKPOINT_CHANGE#不一致,有人立刻断定数据文件坏了,准备跑恢复。

原因:这两个视图的数据来源根本不同。v$datafile读的是控制文件,v$datafile_header读的是数据文件头。在做 backup、restore 或控制文件重建之后,两边记录的检查点 SCN 本来就允许有差异;差异大小取决于最后一次检查点发生的位置和谁先被更新。

解决:先确认刚才是不是做过控制文件相关操作。正常库中两者短期不一致是合理的,长时间不一致且越来越远才需要警惕。排查时用下面这条命令,逐文件核对两次结果:

-- 从数据文件头读检查点 SCN,与 v$datafile 做交叉对比 SELECT file#, checkpoint_change#, checkpoint_time FROM v$datafile_header ORDER BY file#;

5.4 日志切得勤快,检查点却没跟上

现象:日志每十几分钟切换一次,看起来系统很“活跃”,但v$datafile的检查点 SCN 半天不动,ACTIVE 状态的日志越积越多。

原因:日志切换和检查点是两码事。日志写满了就切,切完只代表 redo 落盘结束;检查点则由时间、MTTR 目标、日志切换等多种条件触发,最终要等 DBWR 把脏块刷完才更新文件头。日志切得快但脏块刷不完,检查点就会卡住。

解决:先看 ACTIVE 日志数量,再回到redo_span计算跨度。如果是脏块刷盘跟不上,则需要关注 DBWR 写等待和 buffer cache 是否存在不合理的大扫描把缓存挤爆;盲目增加日志文件大小或切换频率解决不了根因。

5.5 改完 MTTR,性能没变好反而变差

现象:运维同事把fast_start_mttr_target从默认值调到 120 秒,本意是缩短恢复时间,结果业务高峰期的 DB File Sequential Read 等待上涨,查询明显变慢。

原因:恢复时间和运行性能在这个参数上是一个零和博弈。目标时间越短,检查点越积极,脏块提前写盘的频率越高,占用的 I/O 资源就越多。文档原文也强调过,过于频繁的检查点会给高频更新库带来性能问题。

解决:把目标值调回去,或者用SCOPE = MEMORY做临时验证,观察一个完整业务周期再决定是否保留。调参之前务必先看v$mttr_target_advice预估的额外 I/O 量,不要用生产环境试错。

6. 顺手把验证做全:建立“检查点距离”巡检习惯

这篇文档读完之后,真正能留在手里的应该是一套可重复执行的验证动作。整理出来,就是每次做检查或排查时固定先跑下面这段 SQL:

-- 日常巡检:一次性拿到文件级检查点距离和日志边界 SELECT df.file#, df.name, df.checkpoint_change# AS file_cp_scn, (SELECT current_scn FROM v$database) AS db_scn, (SELECT current_scn FROM v$database) - df.checkpoint_change# AS span, df.checkpoint_time FROM v$datafile df ORDER BY span DESC;

判断标准可以套用一条经验规则:任意两次巡检之间,如果span持续放大,说明检查点推进跟不上 redo 生成速度;如果span在一个区间内上下波动,说明系统处于稳态。配合v$log的 ACTIVE 状态日志数量一起看,基本就能判断恢复时间压力是否在积累。

这里再补充一个文档里没有展开、但实测很受用的习惯:拿到任何一份 Oracle 恢复类资料,都先不要背结论,而是把里面的所有 SQL 在测试环境跑一遍,记录真实数字。只有亲自看到 SCN 跳变、检查点推进、日志状态翻转,才能真正明白这三个东西是怎么咬合在一起的。

这份《Oracle SCN与检查点详解》最核心的贡献,是把我从“只听说 Checkpoint 能缩短恢复时间”推进到了“能自己查证检查点走到哪一步”。从那以后,我每次拿到类似的 Oracle 笔记或 DBA 心得,都强制自己先花十分钟把里面的 SQL 全部跑一遍,确认数据长什么样子,再谈优化,而不是照抄参数或命令。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询