干过数据库性能排查的人,大概都见过log file sync霸占 AWR Top 5 的场景。这个等待事件本身不复杂,就是会话提交事务时在等 LGWR 把日志写完。真正复杂的是导致它飙高的根因可以藏得很深,尤其是当系统里跑着 Oracle 12c 的 Adaptive Scalable Log Writer 时,表面上看起来是日志写入瓶颈,实际却可能是这个新特性“帮了倒忙”。我见过不少同行在这个问题上绕了弯路,本文就把这个场景掰开揉碎讲清楚:它是什么、为什么会反噬提交路径、怎么确认、怎么处置。
这个主题适合正在维护 12.1.0.2 或 12.2 以上版本、遇到过 log file sync 异常上升的 DBA,也适合准备上线 12c 并想提前避坑的架构师。我会按真实的排查顺序来写,而不是按官方文档的顺序。
1. 先搞懂 Adaptive Scalable Log Writer 到底做了什么
1.1 从单 LGWR 到“日志写入进程池”的演进
在 10g、11g 时代,日志写入全靠一个 LGWR 进程。它把 redo log buffer 里的内容搬出来,写入当前在线日志文件,写完后通知所有等待在这个提交点上的会话“可以返回了”。这个过程是串行的:一个提交、一次写盘、一次返回。那时候为了提高吞吐,Oracle 引入了 group commit(批量提交)机制,多个会话的提交请求可以攒在一起,一次写盘、统一通知,这也是 log file sync 平均等待能压下来的核心手段。
进入 12c 后,内存里 log buffer 的分配机制变了,引入了多 redo strand 的设计,每个 strand 有独立的分配区域,降低了高并发下的 redo allocation 竞争。到了 12.1.0.2,Oracle 又向前迈了一步,让 LGWR 可以根据负载动态派生出额外的日志写入工作者进程,这就是 Adaptive Scalable Log Writer 的雏形。12.2 里更进一步,引入 Log Writer Process Pool,多个日志写进程可以分别承担不同 redo 区域的写盘工作。
用生活化的类比来说:以前是一个收银员在柜台前给所有顾客结账,人多就得排队,虽然可以一次给好几个人一起结(group commit),但高峰还是吃力。后来改成多个收银员同时开工,听上去吞吐应该上去了,但问题在于——这些收银员共用一本台账,互相之间需要对账、交接、确认进度,一旦协调成本高于结账本身节省的时间,顾客反而等得更久了。log file sync 就是这么被拖高的。
1.2 自适应机制如何决定“扩展”
自适应体现在两处:一是是否启动额外的日志写进程,二是启动几个。Oracle 会根据系统负载、CPU 数量、日志产生速率、提交频率等指标做动态判断。理论上的目标是:当单 LGWR 成为瓶颈时,通过并行写盘来降低日志写入时间,从而缩短提交等待。
这套机制涉及一些内部进程,不仅限于一个。在 12.1.0.2 中,主 LGWR 仍然存在,额外工作者以从属进程形态出现;在 12.2 的进程池模式下,会看到多个lwtr进程同时存在,各自负责独立的 redo 写入区域。这些进程之间需要协调的公共资源包括:log buffer 中 strand 的分配状态、不同写入区间的完成进度、以及通知提交会话“写完了”的信号机制。
我见过一些系统,在 40 核以上的 OLTP 环境里,提交频率很高、单次事务很小,恰好是自适应机制最喜欢“插手”的场景。负载采样器发现 LGWR 的写等待上升,就触发扩容逻辑,派生更多日志写进程。表面看是“对症下药”,实际上在这个读写比例极其敏感的高并发小事务场景下,多进程协调的开销直接叠加到了每次提交的关键路径上,最终 log file sync 非但没降,反而比单 LGWR 时高出 30% 甚至更多。
1.3 设计初衷与实际的偏差
设计者的初衷很明确:通过并行化缩短日志写入时间。但要缩短 log file sync,前提是并行写入带来的收益要大于协调成本。这在以下场景中是成立的:比如日志写入量极大、单个日志文件所在的存储能够支撑真正的并发写、且提交型负载不是唯一主导。但如果存储路径本身是串行化的——在很多中端存储、虚拟化共享磁盘、乃至某些云盘场景下,多个进程并发写同一文件并不比单进程顺序写更快——并行化就只剩下了额外开销。
再叠加另一个因素:自适应扩展和收缩是动态的,进程的启动、停止、状态同步都会引入额外等待。在我的现场观察中,这类系统里 ASH 里除了 log file sync,往往还会出现与进程间通信、共享结构等待相关的事件。如果你在 AWR 里看到 log file sync 和这些“不常见”的等待同时出现,就要高度怀疑是日志写入器扩展机制在捣乱。
2. 怎么确认 log file sync 就是被它“坑”的
2.1 先看现象:哪些典型现场值得怀疑
不是所有 log file sync 飙高都跟 Adaptive Scalable Log Writer 有关,所以第一步是快速过滤出可疑特征。我一般先看五点:
- 数据库版本:12.1.0.2 及以上(12.2 更常见),尤其是 12.2 的 RAC 环境。
- CPU 核数多:通常不低于 32 核,核心多时自适应机制更容易触发并行。
- 负载特征:高并发小事务、频繁短提交,像是支付、订单、会话管理类的业务。
- AWR 中 log file sync 排在 Top 事件前列,且平均等待时间超过 5ms——正常状态应该在 1-3ms 之间。
- 操作系统层面能看到多个
lwtr进程,或者查询v$lgwr_worker_info能看到多个 worker 处于活跃状态。
如果上面几条同时命中,基本可以进入下一步深入验证。但要注意,这还只是“可疑”,不能直接下结论。
2.2 用 AWR/ASH 把根因钉死
要拿到确凿证据,我建议走三条线交叉验证:
第一,确认扩展进程是否真的在工作。查询v$lgwr_worker_info,这个视图会列出日志写入工作者进程的状态、处理的 redo 区域、以及最近的活动统计。如果多个 worker 都有持续的写入活动,说明扩展机制正在活跃使用中。同时可以查v$process或操作系统层确认lwtr进程数量。
第二,统计等待事件分布。从 AWR 报告或者直接查dba_hist_system_event,对比同一时段内log file sync、log file parallel write、log buffer space等事件的变化趋势。这里有个关键点:如果只看到 log file sync 高,而 log file parallel write 并不高,说明磁盘写盘本身不是瓶颈,时间消耗在“协调和通知”上,恰好符合多日志写进程协调开销过大的特征。
第三,做一次“开关对照实验”。在维护窗口,把相关隐藏参数设为关闭值,重启实例,用相同负载跑一段时间,对比 log file sync 的平均等待。这是最直接的判定手段,比任何分析都更有说服力。注意实验前后要保证业务负载形态相近,最好在同一个业务周期内完成。
2.3 确认后要做的备份与回退准备
一旦高度怀疑,不要直接在生产库上改参数。我在处理这类问题时,操作顺序是固定的:
- 先备份当前 spfile:
CREATE PFILE='/tmp/pfile_before_change.ora' FROM SPFILE; - 记录基线数据:当前 log file sync 平均值、Top 等待事件、每秒提交次数。
- 提前写好回退方案:如果关闭后出现新的问题,如何在最短时间内恢复原参数并重启。
- 选择变更窗口:优先选业务低谷,同时预留足够长的观察期,至少覆盖一个业务高峰。
生产环境最怕的不是问题本身,而是改了参数之后说不清楚是变好了还是变差了。所以基线和观察方案必须在动手之前定好。
3. 解决实操:参数调整与效果验证
3.1 关键隐藏参数清单(12.1.0.2 与 12.2 分别处理)
先说结论:解决这个 log file sync 问题的核心,是限制或关闭自适应日志写入器的扩展能力。不同版本要处理的参数不一样。
12.1.0.2 中,主要控制开关是:
_use_adaptive_log_writer_slaves默认值为 TRUE。这个参数控制 LGWR 是否允许动态创建额外的日志写从属进程。要关闭它:
ALTER SYSTEM SET "_use_adaptive_log_writer_slaves"=FALSE SCOPE=SPFILE;12.2 及以后,除了上述参数,还有一个更高层的开关:
_use_adaptive_log_writer以及进程池相关参数,例如限制最高实例数:
_lgwr_pool_hwm_instances如果只是想限制扩展规模而不是完全关闭,可以将_lgwr_pool_hwm_instances设置为 1,相当于只保留一个进程池实例。但我实际遇到过的场景,绝大多数是直接关闭更省心,因为问题是协调开销太大而不是并行不够。
3.2 单实例/ RAC 场景的调整步骤
以 12.2 RAC 两节点为例,完整的操作流程如下。
在所有节点上先执行检查:
SELECT inst_id, name, value FROM gv$parameter WHERE name IN ('_use_adaptive_log_writer_slaves','_use_adaptive_log_writer');然后逐节点修改,注意这里必须是SCOPE=SPFILE:
ALTER SYSTEM SET "_use_adaptive_log_writer_slaves"=FALSE SCOPE=SPFILE; ALTER SYSTEM SET "_use_adaptive_log_writer"=FALSE SCOPE=SPFILE;单实例环境只需要执行一次,RAC 环境要在每个节点上各执行一次(或者使用sid='*'的范围设定)。修改完成后,重启所有实例。对于 RAC,通常是滚动重启,逐个节点重启,保持业务不中断。重启后再次查询参数值和lwtr进程数量,确认已经回落。
这里有个容易踩的坑:很多 DBA 以为ALTER SYSTEM命令执行完就生效了,但这类隐藏参数属于静态参数,必须重启。如果你改完没重启就急着下结论说“没用”,那多半是自己误判了。
3.3 不改参数时还能做什么
关闭参数不是唯一出路,有些场景下业务上不方便重启,或者在重保期间不能动,那就得考虑从其他路径缓解 log file sync。
- 存储 IO 路径:redo 日志文件放独立的高速存储,最好是支持低延迟单块写入的设备。注意“支持并发写”这个前提,如果存储本身对并发写不友好,多进程写反而更糟。
- redo 文件大小:扩大在线日志文件尺寸,减少日志切换频率。切换瞬间的 checkpoint 和文件操作会放大提交延迟,别把它和自适应写进程的影响混在一起。
- 提交参数:评估业务是否能接受
commit_wait和commit_logging的调整,通过延迟写或者批量提交来换取响应时间下降。这个必须和业务团队确认,因为涉及事务持久性语义。 - 应用层:减少无意义的 commit,合并小事务。很多 log file sync 飙升的系统,本质是每秒提交次数太高,给小事务“减频”往往立竿见影。
上述手段可以和关闭自适应写进程组合使用,但我个人建议不要同时上太多变量,否则出了问题很难定位是哪个改动带来的收益或负收益。
3.4 效果验证:读哪些数据才算真的好了
改完参数、重启完,接下来要回答的问题是:到底好没好?判断依据不能只看 log file sync 的数值,我通常看三组数据。
第一组,等待事件变化。log file sync 的平均等待是否回落到正常范围,Top 5 事件里它是否退出了前几名的位置。第二组,应用侧指标。每秒提交次数、事务吞吐量在同等负载下是否保持不变或上升,这点很关键——如果 log file sync 降了但业务吞吐也掉了,说明引入了另一个瓶颈。第三组,日志写入相关进程数量。v$lgwr_worker_info里是否只剩一个活跃进程,OS 层lwtr进程数量是否恢复预期。
观察周期至少要覆盖一个完整的业务高峰,不要只看 10 分钟就下结论。我曾经遇到一个系统,改动后前半小时数据好看,进入业务高峰后反而出现新的等待,原因是关闭扩展进程后单 LGWR 写能力不足。这也说明,关闭自适应写进程并非在所有系统上都正确,有些系统确实需要并行写盘。
4. 常见问题与排查技巧实录
4.1 常见误区:Log file sync 高不等于磁盘慢
这是我见过最多的误区。很多 DBA 一看到 log file sync 高,第一个念头就是“磁盘太慢了,上 SSD”。实际上,log file sync 等待的是 LGWR 完成“写日志并确认”的完整过程,这个时间由三部分组成:日志数据从 log buffer 复制到日志文件的写入时间、写完后的确认通知时间、以及如果存在多写进程时的协调时间。
磁盘 IO 延迟只影响第一部分,而自适应写进程引入的协调开销影响的主要是第二和第三部分。所以会出现一个现象:redo 已经写进了存储,但会话仍然在等待,而且等的时间比写盘时间还长。判断方法是同时看log file sync和log file parallel write两个事件,如果前者远高于后者,意味着问题不在物理写盘,而在写盘之外的通路。
另外一个相关误区是“redo 太小导致切换频繁”。日志切换确实会带来额外延迟,但它一般会在 AWR 中表现为log file switch completion或log file switch (checkpoint incomplete)等事件。如果你看不到这类事件,就不应该把矛头指向 redo 文件大小,别被表象带偏。
4.2 典型场景对照表
| 场景特征 | 主要影响因素 | 推荐动作 |
|---|---|---|
12.2 RAC,高并发小事务,多个lwtr进程活跃 | 写进程池协调开销 | 关闭_use_adaptive_log_writer,必要时保持_use_adaptive_log_writer_slaves=FALSE |
| 12.1.0.2 单实例,多核 OLTP,log file sync 异常高 | 从属写进程同步开销 | 设置_use_adaptive_log_writer_slaves=FALSE |
| redo 存放在 ASM 上,磁盘写延迟低但提交仍慢 | 日志写进程间协调 + 提交通知路径 | 先确认 log file parallel write 时间,若并行写正常则调整写进程参数 |
| 虚拟化共享存储,单卷 IO 能力有限 | IO 本身无法并发提速 | 不盲目开并行,优化存储配置或减少提交频率 |
| 业务侧每秒提交次数极高 | commit 路径整体压力 | 应用层合并事务,配合关闭自适应写进程 |
4.3 几个让我印象深刻的坑
第一个坑:改完参数没重启,白白折腾了一个维护窗口。前面已经提过,但如果再强调一遍——这个参数改动后不会在线生效,你执行完ALTER SYSTEM后查v$parameter看到的值还是旧的,千万别误判。12.2 之前,某些版本对_use_adaptive_log_writer_slaves的改动可以动态修改,但 12.2 的进程池相关参数基本都要重启,操作前先确认版本行为。
第二个坑:单独设置一个参数不生效。我在 12.2 RAC 上处理过一个案例,只把_use_adaptive_log_writer设为 FALSE,启动后lwtr进程数量还是多于一个。后来把_use_adaptive_log_writer_slaves也一起关闭,才真正回到单进程模式。所以 12.2 上两个参数最好一起改动,然后再验证进程数量。
第三个坑:关闭自适应写进程后,log buffer 空间的等待上升。某系统关闭扩展后 log file sync 确实降下来了,但log buffer space开始冒头。原因很简单,少了一个写进程,log buffer 的消耗速度变慢,缓冲区压力变大。解决办法是适当增大 log buffer,或者从应用侧降低提交频率。这也提醒我,调优动作做完必须跟踪周边事件,不能只看目标指标。
5. 个人经验:何时该怀疑它,何时该先查别的
5.1 我的快速判定路径
如果你现在正被 log file sync 困扰,我建议按这条路来定位,十分钟内就能给出方向:
- 先看版本。低于 12.1.0.2 基本和本文主题无关,直接查 IO 延迟和提交频率。
- 12.1.0.2 以上,查
v$lgwr_worker_info和 OS 层lwtr进程数。如果活跃 worker 只有 0 或 1 个,这个特性大概率不是主因。 - 对比 log file sync 和 log file parallel write。差别越大,越说明瓶颈不在磁盘。
- 看每秒提交次数。低并发、低提交频率的系统,自适应写进程没机会捣乱。
- 全部可疑条件命中后,再考虑做开关实验。不建议上来就动参数,先把观察做足。
5.2 哪些系统不适合一刀切关闭
说了这么多“关掉它”,但我也得实话实说:不是所有系统都适合关闭。判断依据是每秒日志产生量以及写入路径的特性。如果一个系统的 redo 生成量极大,单 LGWR 确实跟不上写入要求,而存储又支持并发写,那么保留自适应扩展、但把进程数限制在一个合理范围,可能比彻底关闭更合适。可以用_lgwr_worker_count或_lgwr_pool_hwm_instances这类参数来控制扩展规模,而不是消灭它。
如果你的系统恰好是高并发小事务、提交频繁的典型 OLTP,而自适应写进程带来的协调开销已经成为主要矛盾,那就不要犹豫,关闭扩展、回归单写进程模式,回到 group commit 的主航道上来。这个取舍,本质上是在“并行写盘的吞吐收益”和“多进程协调的延迟代价”之间做权衡。搞清楚这两者的关系,我就不信你还会被 log file sync 的名字吓住。
最后分享一个每次遇到这类问题我都会提醒自己的经验:数据库参数只是表象,存储特性和负载形态才是决定因素。你可以轻松改掉一个隐藏参数,但改完之后系统是否真的变好,取决于你对自己环境里“写入到底快在哪、慢在哪”的理解有多深。多花几分钟看数据,比急着敲命令可靠得多。