DBA夜里被叫醒的场景,十有八九跟两个后台进程有关:一个负责收拾进程死掉后的烂摊子,一个负责实例崩溃后的整体复原。Oracle 的 SMON(System Monitor)与 PMON(Process Monitor)就是数据库里这对“守护双星”,名字听着平淡,真出事的时候它们决定了库是几分钟起来还是几十分钟趴着。这篇文章不打算复述官方文档的职责列表,而是从我这几年处理实例异常、进程僵死、临时段膨胀这些现场经验出发,把 PMON 的进程清理链路、SMON 的实例恢复生命周期,以及两者在故障边界的接力关系拆开讲。无论你是刚接手 Oracle 运维的新手,还是想搞清楚“为什么日志里老出现 SMON enabling cache recovery”的老手,看完都能对这两个进程建立起一张可观测、可干预、可排障的完整地图。
1. 先把两个后台进程的职责边界划清楚
很多人把 SMON 和 PMON 混在一起记,觉得都是“后台清理工”。这种模糊认知在平时不出问题,一旦遇到实例恢复慢或者锁等待堆积,就会判断错方向。我的经验是先把边界钉死:PMON 管“进程级”的生死,SMON 管“实例级”的存续。两者都跟恢复有关,但触发条件、作用范围、可调参数完全不同。下面按职责拆开讲,最后再说它们在什么节点上会交接。
1.1 SMON 到底管什么:从实例恢复到空间回收
SMON 在实例启动的 nomount 阶段就被拉起,但它真正开始干活是在 mount 阶段。第一个重头戏就是实例恢复:数据库上次非正常关闭时,redo 里还留着没来得及写到数据文件的变化,SMON 要把这些变化前滚,再把未提交的事务回滚。这个过程你在告警日志里会看到一串固定格式的输出,从 “SMON: enabling cache recovery” 开始,到 “Completed: alter database open” 结束。中间如果涉及并行恢复,还会出现 slave 进程参与的信息。
日常运行中,SMON 的活没那么惊心动魄,但同样重要。它负责清理临时段:一个排序操作或全局临时表用完后,临时段不会立刻消失,SMON 会定期扫描并释放;它负责合并表空间里的空闲区,避免碎片越积越多;它还会清理 OBJ$ 表里已经删除对象的残留记录,把字典管理表空间里离线段的 extents 标记为可用。这些操作分散在后台,平时感知不到,但一旦 SMON 被阻塞,你会看到临时表空间只涨不降、字典表查询变慢、甚至空间不足的告警。
提示:SMON 的日常清理是分批次做的,不是一次性扫全库。所以临时段暴涨之后不要指望它马上回落,观察窗口通常要按小时算。
这里有个容易误解的点:SMON 的实例恢复只发生在“实例崩溃后重新启动”这个场景。如果是单个用户进程被杀掉,走的是 PMON 的清理路径,跟 SMON 无关。很多新手看到回滚就以为是 SMON 在干活,其实要看故障粒度——进程级交给 PMON,实例级才轮到 SMON 登场。
1.2 PMON 到底管什么:进程级清理与服务注册
PMON 是实例启动时最早起来的后台进程之一,它像一个大楼的物业管理员,盯着每个进出的人。用户进程正常退出,它负责收尾;用户进程异常死掉,它负责善后。善后的动作包括:回滚该进程未提交的事务、释放它持有的锁和 enqueue 资源、把它从 V$PROCESS 和 V$SESSION 里抹掉、释放 PGA 内存。这一套动作串起来,就是一次“死进程清理”。
除了清理,PMON 还承担服务注册的职责。数据库实例启动后,PMON 会周期性地把实例名、服务名、负载信息推送给监听器,监听器才知道有新实例可以接受连接。你如果遇到“监听器起来了但客户端连不上”,或者 “lsnrctl status” 里服务状态显示 READY 但实例名对不上,多半是 PMON 的注册链路出了问题,而不是监听器本身坏了。
PMON 清理的耗时跟死进程当时干了多少活直接相关。一个只读查询进程被杀,清理几乎是瞬时的;一个刚更新了几百万行还没提交的进程被杀,PMON 要回滚这些变化,耗时可能达到分钟级甚至更长。这段时间里,相关行上的锁不会释放,其他会话会卡在 enqueue 等待上。所以看到大量 “enq: TX - row lock contention” 而持有人又是空会话时,第一反应应该是查 PMON 是不是正在清理一个“大块头”。
1.3 两者在进程生命周期中的接力点
SMON 和 PMON 并非各干各的,它们在几个关键节点上有明确的分工和接力。第一个接力点是实例启动:PMON 先起,负责把实例的进程框架搭起来;SMON 随后进入 mount 阶段执行恢复,恢复完成后数据库才 open,PMON 的服务注册也才真正对外可用。这个顺序不能乱,否则客户端可能在恢复完成前就连进来。
第二个接力点在故障判定的粒度上。单个进程死亡,PMON 独立处理,SMON 不介入;但如果是整个实例崩溃(比如主机断电、实例被 abort),所有进程一起没了,PMON 没机会做进程级清理,这个烂摊子就整体移交给下次启动时的 SMON。换句话说,PMON 处理“局部死亡”,SMON 处理“整体死亡”。
第三个接力点体现在事务回滚的资源竞争上。PMON 清理死进程时如果回滚量特别大,会消耗大量 CPU 和 IO,可能拖慢整个实例;SMON 的并行恢复同样会占用资源。两者都需要通过参数和资源管理器做约束,避免“清理动作把业务压垮”这种次生故障。理解了这三个接力点,排查时就不会把 PMON 的锁等待误判成 SMON 的恢复问题。
2. PMON 的清理链路:从死进程到资源释放
PMON 的清理过程是一条有先后顺序的链路,不是简单地把进程信息删掉。链路里任何一环卡住,表现出的症状都不一样。这一章按“检测—回滚—释放—注销”的顺序拆开讲,同时补上服务注册的细节和可以直接上手的观测手段。
2.1 一次进程异常终止后 PMON 做了什么
假设一个会话执行了大更新后客户端异常断开,或者被 DBA 用 kill 命令强杀。PMON 检测到进程消失后,第一步是标记该进程为死进程,阻止它继续持有任何资源;第二步是回滚它未提交的事务。回滚不是把数据改回去那么简单,而是反向应用 undo 记录,逐条撤销变化。这个阶段在 V$TRANSACTION 里能看到状态变成 ROLLBACK,V$SESSION 里会话可能还残留但状态是 KILLED。
回滚完成后,PMON 释放该进程持有的所有 enqueue 锁,包括 TX、TM、library cache 相关的锁。很多“杀会话后业务还是卡着”的案例,就是回滚还没走完,锁还没放。接着 PMON 清理进程的 PGA 内存,把 V$PROCESS 里的记录删除,最后通知相关模块做收尾。整个链路走完,一次死进程清理才算闭环。
注意:用 alter system kill session 杀会话时,如果会话正在执行大事务,命令会立即返回,但实际清理在后台异步进行。不要以为命令返回就万事大吉,必须观察 V$TRANSACTION 和锁视图确认回滚完成。
这里有个实操细节值得记:PMON 回滚大事务时,如果中途实例又崩了,回滚做到一半的状态会留在 undo 里,下次启动由 SMON 接手继续回滚。也就是说,PMON 和 SMON 在事务恢复这件事上是共享同一套 undo 机制的,只是触发场景不同。理解了这一点,就不会困惑于“为什么重启后还要继续回滚”。
2.2 服务注册与监听器的心跳细节
PMON 向监听器注册服务的过程,本质上是周期性推送实例状态。注册内容包括实例名、服务名、当前负载、可用连接数等。监听器收到后更新自己的服务列表,客户端才能通过服务名路由到正确实例。注册是周期性的,所以实例刚启动时可能有一小段窗口监听器还看不到服务,这是正常的,不是故障。
如果注册失败,常见原因有三类:一是 local_listener 参数配置错误,PMON 不知道往哪个地址推;二是监听器端口或协议不匹配,推过去被拒绝;三是网络层拦截,包发不出去。排查时先看监听器日志有没有注册相关报错,再用 lsnrctl services 看服务状态是否与实例实际情况一致,最后检查参数配置。
提示:RAC 环境下服务注册还涉及远程监听器,remote_listener 配错会导致节点间服务发现异常,表现为某个节点的服务时有时无。这类问题优先核对参数,而不是重启监听器。
我一般会把这套检查做成一个固定动作:实例启动后等一到两个注册周期,再确认监听器状态。如果急着做连通性测试,很可能在注册完成前就下结论,白白绕一圈。注册周期可以通过参数调整,但生产环境不建议为了“快一点看到服务”而改得太频繁,会增加监听器负担。
2.3 PMON 清理的观测手段与实操参数
观测 PMON 的清理行为,最直接的是看 V$PROCESS 和 V$SESSION 中状态异常的记录,结合 V$TRANSACTION 判断是否处于回滚中。还可以关注 enqueue 锁视图,确认锁的持有者是不是已经死掉的进程。告警日志里偶尔会出现 PMON 相关的清理信息,但不像 SMON 的恢复日志那么显眼,需要主动去翻。
对于清理特别慢的场景,可以对 PMON 做 trace 或者抓 systemstate dump,看它卡在哪个调用上。常用手法是用 oradebug 挂到 PMON 的 OS 进程上,开 10046 事件,观察回滚进度。systemstate dump level 3 能看到进程栈和锁等待关系,判断是不是有资源竞争拖慢了清理。
参数层面,PMON 本身可调的公开参数不多,更多是通过资源管理器和会话限制间接约束。比如限制单会话的 undo 使用量、给大事务设置合理的回滚段策略,都能降低 PMON 清理的负担。我的经验是:与其等 PMON 清理时手忙脚乱,不如在业务侧控制事务粒度,大更新分批提交,从源头上减少“大块头”死进程出现的概率。
3. SMON 的空间管理与实例恢复分工
SMON 的工作可以分成“日常维护”和“应急恢复”两块,前者是长期低强度的后台任务,后者是启动时集中爆发的高强度任务。两块共用同一个进程框架,但资源消耗和可观测性差异很大。这一章先讲日常维护的细节,再拆实例恢复的两阶段流程,最后说并行恢复的演进和参数控制。
3.1 临时段、空闲区与 OBJ$ 的日常维护
临时段清理是 SMON 最频繁的日常工作。排序、哈希连接、全局临时表都会申请临时段,用完后段不会立即释放,而是标记为可复用。SMON 定期扫描临时表空间,把不再需要的段真正释放掉。如果临时表空间只涨不降,除了检查 SQL 是否滥用排序,还要确认 SMON 的清理是否被阻塞。阻塞的常见原因是临时表空间里有长时间运行的会话占着段不放,或者 SMON 自身因为资源竞争被饿死。
空闲区合并是另一项容易被忽视的工作。表空间里的 extents 被释放后,如果不合并,会形成很多小碎片,后续申请大空间时可能失败。SMON 会把相邻的空闲 extents 合并成更大的块。字典管理表空间时代这个问题很突出,本地管理表空间普及后压力小了很多,但在老系统里仍然值得关注。
OBJ$ 表清理针对的是已删除对象的残留记录。删除一张表后,字典里的记录不会立刻消失,SMON 会在后台慢慢清理。如果一次删了大量对象,OBJ$ 会膨胀,相关字典查询变慢,严重时可能影响 DDL 操作。观察到字典表空间异常增长,可以往这个方向查。
3.2 实例恢复的前滚与回滚两阶段
实例恢复是 SMON 最核心的能力,分两个阶段:前滚(cache recovery)和回滚(transaction recovery)。前滚阶段,SMON 从最后一个检查点开始,把 redo 日志里记录的所有变化重新应用到数据文件,包括已提交和未提交的事务。这一步的目的是让数据文件恢复到崩溃前一刻的内存状态。前滚完成后,数据文件里既有已提交的变化,也有未提交的变化,处于不一致状态。
回滚阶段,SMON 识别出崩溃时未提交的事务,用 undo 记录把它们逐个撤销。撤销完成后,数据文件才回到一致状态,数据库才能 open。这两个阶段的顺序不能颠倒,前滚必须覆盖所有 redo,回滚必须基于前滚后的状态。告警日志里的恢复信息会隐约反映这个顺序,前滚阶段是 “cache recovery”,回滚阶段是 “transaction recovery”。
注意:实例恢复的前滚是“重放所有变化”,不是“只重放已提交的”。这一点跟很多人的直觉相反,也是理解恢复时间估算的关键——redo 量越大,前滚越久,跟提交与否无关。
前滚的起点由检查点决定。检查点越新,需要重放的 redo 越少,恢复越快。所以 checkpoint 的频率直接影响恢复时间,这就是 MTTR 参数存在的意义。但检查点太频繁会增加正常运行的 IO 负担,需要在恢复速度和运行性能之间找平衡。
3.3 并行实例恢复的演进与参数控制
单进程做实例恢复,在 redo 量大的场景下会非常慢。Oracle 从很早的版本就引入了并行恢复,SMON 可以拉起多个 slave 进程同时做前滚和回滚。并行度不是随便设的,它受 CPU 数量、IO 能力、参数配置共同约束。恢复太慢时,适当提高并行度能明显缩短 open 时间;但并行度过高会造成资源争抢,反而拖慢整体。
控制恢复时间的核心参数是 FAST_START_MTTR_TARGET,它设定一个目标恢复时间(秒),数据库据此自动调整检查点频率。设得太小,检查点过于频繁,正常运行受影响;设得太大,恢复时间没保障。我的做法是先观察一段时间的实际 MTTR,再结合业务对停机时间的容忍度来定,不要一上来就拍一个激进的值。
还有一个视图值得常用:V$INSTANCE_RECOVERY,里面能看到当前估算的恢复时间、检查点进度、并行恢复的状态。实例异常重启后,如果 open 很慢,先查这个视图确认恢复进行到哪一步,再决定是等还是干预。并行恢复的 slave 进程信息也能在相关视图里看到,判断并行度是否生效。
4. 实例恢复完整生命周期的现场复盘
前面讲的是机制,这一章换成一个现场视角:假设一个实例因为主机异常断电而崩溃,从重新启动到完全 open,整个过程经历了什么,每个阶段能观察到什么,参数怎么影响时间线。这部分内容是我处理这类故障时总结的复盘模板,可以直接拿去对照自己的环境。
4.1 从异常掉电到数据库打开的时间线
实例崩溃后的重启分几个阶段。第一个阶段是 nomount,PMON 等后台进程先起来,实例框架搭好,但还没挂载数据库。第二个阶段是 mount,SMON 开始执行实例恢复,先做前滚,再做回滚。这个阶段数据库还不能接受业务连接,但可以查 V$INSTANCE 和 V$INSTANCE_RECOVERY 观察进度。第三个阶段是 open,恢复完成,数据文件一致,PMON 的服务注册生效,客户端可以连入。
时间线上的大头通常在 mount 阶段。前滚的耗时取决于检查点之后积累的 redo 量,回滚的耗时取决于崩溃时未提交事务的规模。如果崩溃前刚好有一批大事务在跑,回滚阶段可能拖得很长。这解释了为什么有些实例重启几分钟就 open,有些要几十分钟——不是数据库“变慢了”,而是需要重放和撤销的工作量不同。
提示:恢复期间不要频繁重启实例。每次重启都会让恢复从头再来一遍,前功尽弃,还可能因为反复崩溃导致更严重的损坏。
我在现场会做一件事:记录每个阶段的时间戳,和 V$INSTANCE_RECOVERY 里的估算值对照。如果实际耗时远超估算,说明有额外因素拖慢,比如 IO 瓶颈、并行度没生效、或者恢复过程中又有进程异常。这种对照能快速定位问题,比盲目等待有效得多。
4.2 关键参数计算与恢复时间估算
恢复时间不是玄学,可以用参数和视图估算。核心思路是:前滚时间约等于需要重放的 redo 量除以 redo 应用速率,回滚时间约等于未提交事务的 undo 量除以回滚速率。redo 量由检查点位置决定,未提交事务量在崩溃瞬间是未知的,只能靠日常控制事务粒度来降低风险。
FAST_START_MTTR_TARGET 的作用是反过来推检查点频率:设定目标恢复时间后,数据库根据当前负载自动决定多久做一次检查点,保证累积的 redo 量不超过目标时间能重放完的量。设成 300 秒,意味着数据库会努力让恢复时间控制在 5 分钟左右。但这个值受 IO 能力限制,设得太小会导致检查点过于频繁,正常写入被拖慢。
实际配置时,我会先用 V$INSTANCE_RECOVERY 观察当前估算的 MTTR,再结合业务停机容忍度调整。如果业务要求 RTO 在 5 分钟内,就把目标设在 300 秒以内,同时监控检查点带来的 IO 增量。如果 IO 扛不住频繁检查点,就要在恢复时间和运行性能之间妥协,或者从存储层提升 IO 能力。这个过程没有标准答案,只有适合当前环境的平衡点。
4.3 恢复过程中的日志与视图观测
告警日志是恢复过程最忠实的记录者。从 “Starting ORACLE instance” 到 “SMON: enabling cache recovery”,再到 “Instance recovery: looking for dead threads”,最后 “Completed: alter database open”,每一行都对应一个阶段。日志里如果出现反复的恢复信息,说明实例不稳定,需要查根因而不是每次都等它恢复完。
视图层面,V$INSTANCE_RECOVERY 提供估算的 MTTR、检查点进度、目标 MTTR;V$RECOVERY_PROGRESS 能看到恢复的百分比和当前阶段;V$FAST_START_SERVERS 能看到并行恢复的 slave 进程。恢复卡住时,这几个视图组合起来看,能判断是卡在前滚还是回滚,是单进程还是并行,是有资源竞争还是单纯量大。
我还会关注系统层面的 IO 和 CPU。恢复是重 IO 操作,如果存储延迟高,前滚会明显变慢;如果 CPU 被其他进程占满,并行恢复的 slave 抢不到资源,并行度形同虚设。把数据库视图和系统监控对照着看,才能判断瓶颈到底在哪一层。
5. 故障现场:常见问题与排查实录
理论和流程讲完,最后落到实际会遇到的问题上。这一章整理几类高频故障现象、排查路径和我的处理心得,尽量给出可以直接照做的动作,而不是泛泛的原则。
5.1 典型问题速查表
下面这张表是我这些年遇到过的、跟 SMON 和 PMON 直接相关的典型问题,按现象归类,附上排查入口和处理方向。
| 现象 | 可能原因 | 排查入口 | 处理方向 |
|---|---|---|---|
| 杀会话后业务仍卡在行锁等待 | PMON 正在回滚大事务,锁未释放 | V$TRANSACTION 状态、enqueue 锁视图 | 等待回滚完成,避免重复杀会话 |
| 实例重启后 open 极慢 | 前滚 redo 量大或回滚事务多 | V$INSTANCE_RECOVERY、告警日志 | 检查 MTTR 参数,评估并行度 |
| 监听器看不到服务 | PMON 注册失败 | 监听器日志、local_listener 参数 | 核对注册地址和端口 |
| 临时表空间只涨不降 | SMON 临时段清理被阻塞 | 临时段使用视图、SMON 资源占用 | 排查长事务和资源竞争 |
| 字典表空间异常膨胀 | OBJ$ 清理滞后 | 字典表空间使用率、对象删除记录 | 控制批量删除节奏 |
| 恢复日志反复出现 | 实例反复崩溃或不稳定 | 告警日志时间线、OS 层日志 | 查根因,避免反复重启 |
这张表不是穷举,但覆盖了最常见的几类。实际排查时,现象往往交叉出现,比如 PMON 清理慢会连带引发锁等待和业务超时,需要顺着链路往下查,而不是只盯着一个点。
5.2 我踩过的坑与独家心得
第一个坑是“杀会话杀出连锁反应”。早年遇到一个会话占着锁不放,我直接强杀,结果它正在跑一个大事务,PMON 回滚了十几分钟,期间相关表上的业务全部阻塞。后来我学乖了:杀会话之前先看它在事务里干了多少活,能等它自己提交或回滚就等,非杀不可时挑业务低峰,并且提前通知相关方。这个习惯帮我避免了好几次次生故障。
第二个坑是“把 SMON 恢复慢当成数据库坏了”。有一次实例崩溃后 open 花了二十多分钟,我一度怀疑数据文件有问题,后来查 V$INSTANCE_RECOVERY 才发现是崩溃前有一批大事务在跑,回滚量大。从那以后我会在崩溃重启时先看估算值,再决定是等还是介入,不再凭感觉判断。
提示:日常就把 FAST_START_MTTR_TARGET 和事务粒度管好,比出事后再想办法强。恢复能力是设计出来的,不是临时调出来的。
第三个心得是关于观测习惯的。很多人只在出事时才去看视图和日志,其实平时就应该建立基线:正常情况下 MTTR 估算多少、检查点多频繁、临时段清理周期多长。有了基线,异常一眼就能看出来;没有基线,所有数据都是孤立的,判断全靠猜。我现在维护的每套库都有一份这样的基线记录,平时花几分钟,出事时省几个小时。
最后一个建议是慎用强制手段。无论是杀会话、重启实例还是调整恢复参数,都要先想清楚后果链条。SMON 和 PMON 的设计哲学是“后台默默把事做完”,大多数时候给它们时间和资源,它们能自己收敛。人为干预的价值在于判断“该不该等”和“等的时候做什么”,而不是替它们干活。把这两个进程的行为模式摸透,夜里被叫醒的次数会明显少很多。