WR Blog 加载中...
返回文章
数据库OracleAWR性能优化

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量封面

数据库基础体系 · 第 112/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量

Oracle 性能问题通常不是“某条 SQL 慢”这么简单。一次请求从客户端到数据库,可能经历连接排队、CPU 争用、逻辑读、物理读、锁等待、日志提交、并行执行、网络传输和存储响应。要判断问题,必须同时回答四个问题:

  1. 数据库当时在做什么?
  2. 时间花在哪里?
  3. 优化器为什么选择这个执行计划?
  4. 当前资源是否已经接近容量边界?

Oracle 提供了几组互补的观测机制:

  • AWR(Automatic Workload Repository):以快照为边界保存一段时间内的数据库工作负载摘要。
  • ASH(Active Session History):以活动会话为对象,记录会话在某个时间点正在运行或等待什么。
  • 等待事件(Wait Events):描述会话为什么没有继续执行。
  • 统计信息(Optimizer Statistics):描述表、列、索引等对象的数据分布,供优化器估算执行代价。
  • 容量分析:把吞吐量、响应时间、CPU、I/O、并发和数据库时间联系起来,判断系统距离瓶颈还有多少余量。

这些机制的观察对象不同。AWR 和 ASH 不能替代执行计划,等待事件也不能直接证明某个对象有问题,统计信息更不是运行时性能计数器。


一、先区分几种“时间”:响应时间、数据库时间和资源时间

1. 响应时间不等于数据库时间

一次请求的响应时间可以抽象为:

R=Rqueue+Rapplication+Rnetwork+RdatabaseR = R_{\text{queue}} + R_{\text{application}} + R_{\text{network}} + R_{\text{database}}

其中:

  • RR:用户感知的端到端响应时间;
  • RqueueR_{\text{queue}}:请求在连接池、线程池或其他队列中的等待时间;
  • RapplicationR_{\text{application}}:应用代码执行时间;
  • RnetworkR_{\text{network}}:网络传输时间;
  • RdatabaseR_{\text{database}}:数据库内部处理时间。

Oracle 主要观察数据库内部部分。即使 AWR 显示数据库负载正常,应用仍可能因为连接池耗尽或网络延迟而超时。

Oracle 中常见的 DB time 是所有前台会话处于活动状态时消耗的时间总和。活动状态通常包括:

  • 正在使用 CPU;
  • 正在等待非空闲等待事件。

如果同一时刻有 10 个会话各自活动 1 秒,DB time 大约增加 10 秒,而墙上时钟只过去 1 秒。因此:

平均活动会话数(AAS)=DB time墙上时间\text{平均活动会话数(AAS)} = \frac{\text{DB time}}{\text{墙上时间}}

AAS 是性能分析中的核心量。它回答的是:在一个时间区间内,平均有多少个前台会话正在消耗数据库资源或等待数据库资源。

2. 一个完整算例

假设某小时的 AWR 报告给出:

  • DB time:7200 秒;
  • DB CPU:3600 秒;
  • 非空闲等待时间约为 3600 秒;
  • 该区间长度:3600 秒。

则:

AAS=72003600=2AAS = \frac{7200}{3600} = 2

平均有 2 个前台会话处于活动状态。

进一步可以分解:

DB timeDB CPU+非空闲等待时间\text{DB time} \approx \text{DB CPU} + \text{非空闲等待时间}

于是:

  • CPU 占 DB time:3600/7200=50%3600 / 7200 = 50\%
  • 非空闲等待占 DB time:3600/7200=50%3600 / 7200 = 50\%

这并不表示数据库只用了 50% 的主机 CPU。DB CPU 是所有会话累计使用的 CPU 时间,而主机 CPU 使用率还受到后台进程、其他进程、CPU 核数和虚拟化调度的影响。

如果数据库运行在 8 个逻辑 CPU 上,那么 3600 秒 DB CPU 分布在 3600 秒墙上时间中,平均相当于约 1 个 CPU 核:

平均 CPU 核占用=DB CPU墙上时间=1\text{平均 CPU 核占用} = \frac{\text{DB CPU}}{\text{墙上时间}} = 1

但这仍然不能直接推出“CPU 没有问题”。如果业务高峰只有 5 分钟,峰值 CPU 可能远高于小时平均值;如果运行在受限的容器 CPU 配额中,8 个宿主机 CPU 也不代表数据库可以使用 8 个 CPU。

3. CPU 不是等待事件

Oracle 会话状态常见为:

  • ON CPU:正在 CPU 上执行;
  • WAITING:正在等待某个事件;
  • 其他状态:例如会话未处于数据库活动执行阶段。

CPU 消耗不会以一个叫作 CPU 的等待事件出现。分析时必须同时看:

  • DB CPU;
  • 前台等待时间;
  • 活跃会话数量;
  • 主机 CPU、运行队列和 CPU 配额;
  • SQL 的执行次数和单次资源消耗。

“没有明显等待事件”不代表没有性能问题,可能是 CPU 饱和、SQL 做了过多逻辑读,或者应用端没有把数据库时间记录完整。


二、AWR:以快照比较工作负载变化

1. AWR 记录什么

AWR 是 Oracle 自动工作负载资料库。它周期性记录数据库状态和工作负载摘要,典型内容包括:

  • 系统级时间模型统计;
  • 系统等待事件;
  • 前台和后台统计;
  • SQL 的执行次数、逻辑读、物理读、CPU 时间、数据库时间等;
  • 实例活动和部分资源统计;
  • 对象级访问或变更统计;
  • 相关的优化器和系统信息。

AWR 的基本数据流是:

运行中的内存统计
        │
        │ 生成快照
        ▼
AWR 持久化历史数据
        │
        │ 比较两个快照
        ▼
区间报告:增量、排名、趋势

AWR 不是实时监控工具。它记录的是快照之间的累计变化,因此一个小时的报告可能掩盖五分钟内发生的尖峰。

AWR 的诊断数据通常保存在数据库内部的相关表空间中。快照具有:

  • 快照编号;
  • 开始时间;
  • 结束时间;
  • 实例标识;
  • 数据库标识。

在 RAC 环境中,需要注意数据库级和实例级视角,查询通常需要使用 GV$ 视图或在正确的实例上生成报告。

2. 快照间隔和保留时间不是性能结论

Oracle 环境中的快照间隔和保留时间可以配置。某些安装的常见默认值是每小时生成一次并保留若干天,但具体值取决于版本、安装方式和管理员配置,不能把默认值当作规范保证。

可以通过以下查询查看配置:

SELECT snap_interval,
       retention,
       topnsql
FROM   dba_hist_wr_control;

这些字段分别描述快照间隔、保留策略和 SQL 记录范围。查询通常需要相应的数据字典权限。

在生产环境中,修改 AWR 控制参数会影响存储量和诊断能力,不应为了“让报告更详细”而无限缩短间隔或无限延长保留时间。更高的采样或保留成本需要结合问题发生频率、存储空间和合规要求评估。

3. AWR 报告的本质是“两个状态的差分”

例如,某 SQL 在快照 100 时累计:

  • 执行次数:1000;
  • DB time:500 秒;
  • 逻辑读:1,000,000。

在快照 101 时累计:

  • 执行次数:1100;
  • DB time:900 秒;
  • 逻辑读:1,800,000。

那么区间增量为:

  • 执行次数:100;
  • DB time:400 秒;
  • 逻辑读:800,000。

区间平均值为:

平均每次执行 DB time=400100=4 秒\text{平均每次执行 DB time} = \frac{400}{100} = 4\text{ 秒}

平均每次执行逻辑读=800000100=8000 次\text{平均每次执行逻辑读} = \frac{800000}{100} = 8000\text{ 次}

不能直接用累计值判断当前区间性能,否则会把数据库启动以来的历史工作负载混入结论。

4. 生成 AWR 报告

Oracle 常见环境中可以使用 SQL*Plus 提供的报告脚本:

@?/rdbms/admin/awrrpt.sql

脚本通常会依次询问:

  1. 报告类型,例如单实例或 RAC;
  2. 报告格式;
  3. 起始快照;
  4. 结束快照;
  5. 输出文件名。

它要求执行者具有足够的访问权限,并且环境必须具备相应的 AWR 能力。

报告结果应按以下顺序阅读:

  1. 报告时间范围和数据库实例;
  2. Load Profile;
  3. Top Timed Events;
  4. Time Model Statistics;
  5. SQL 统计;
  6. Instance Activity;
  7. 主机 CPU 和 I/O 信息;
  8. 与业务峰值、发布、批处理时间对照。

不要看到 Top Timed Events 的第一项就立刻修改参数。它只说明某个事件在该时间区间累计消耗较多时间,还需要结合执行次数、并发量、调用链和资源上限。

5. AWR 的许可证边界

AWR、ASH 以及相关历史诊断能力属于 Oracle Diagnostics Pack 的范围。是否可以在某个环境中使用,取决于 Oracle 版本、版本选项、部署方式和许可证合同。不能因为视图存在就默认可以在任何环境中自由使用。

在生产环境使用 AWR 报告、历史 ASH 或相关接口前,应由负责 Oracle 许可证的团队确认授权范围。本文讨论的是功能语义,不替代许可证判断。


三、ASH:回答“某一时刻哪些会话在做什么”

1. ASH 的观察单位是活动会话

ASH,即 Active Session History,面向“活动会话”采样。一个样本通常包含类似信息:

  • 样本时间;
  • 会话标识;
  • SQL 标识;
  • 会话当前是否在 CPU 上;
  • 等待事件和等待参数;
  • 等待类别;
  • 当前对象或数据文件等上下文;
  • 阻塞会话相关信息;
  • 服务名、模块、动作等会话属性。

ASH 的重要特点是:

  • 它不是每次等待开始和结束都完整记录;
  • 它主要记录活动会话;
  • 它通常以约一秒级的采样粒度观察活动;
  • 短于采样间隔的瞬时事件可能被漏采;
  • 长时间持续的等待更容易被观察到。

因此,ASH 是抽样历史,不是完整审计日志。某条 SQL 没出现在 ASH 中,不一定代表它从未执行过,可能是执行太快、未处于活动状态,或者历史数据已被覆盖。

2. 当前 ASH 与历史 ASH

当前内存中的活动会话历史可通过 V$ACTIVE_SESSION_HISTORY 查询。历史持久化数据通常通过 DBA_HIST_ACTIVE_SESS_HISTORY 查询。

一个查看最近活动会话的示例:

SELECT sample_time,
       session_id,
       session_serial#,
       session_state,
       event,
       wait_class,
       sql_id,
       module
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '10' MINUTE
ORDER BY sample_time DESC
FETCH FIRST 100 ROWS ONLY;

需要注意:

  • V$ACTIVE_SESSION_HISTORY 的可见历史受内存和当前负载影响;
  • 历史视图需要相应权限和许可;
  • FETCH FIRST 的使用要符合目标数据库版本;
  • 在 RAC 中通常应考虑 GV$ACTIVE_SESSION_HISTORY,并显示 INST_ID

按等待类别聚合:

SELECT wait_class,
       COUNT(*) AS samples
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '15' MINUTE
GROUP BY wait_class
ORDER BY samples DESC;

这里的 samples 不是等待次数,也不是精确的等待秒数。若采样间隔近似为 1 秒,可以把样本数量作为活动时间的近似,但仍然要明确这是抽样估计。

3. 用 ASH 找到“谁、何时、因为什么”

例如,某接口在 10:00 到 10:05 变慢,可以按 SQL 和等待类别分组:

SELECT sql_id,
       session_state,
       wait_class,
       event,
       COUNT(*) AS samples
FROM   v$active_session_history
WHERE  sample_time >= TO_TIMESTAMP('2025-01-01 10:00:00',
                                   'YYYY-MM-DD HH24:MI:SS')
AND    sample_time <  TO_TIMESTAMP('2025-01-01 10:05:00',
                                   'YYYY-MM-DD HH24:MI:SS')
GROUP BY sql_id, session_state, wait_class, event
ORDER BY samples DESC;

分析步骤是:

  1. 先确定时间窗口,而不是只看“最近”;
  2. 找出活动样本最多的 SQL;
  3. 区分 ON CPUWAITING
  4. 对等待样本按 wait_classevent 聚合;
  5. 回到 SQL 执行计划和业务调用次数;
  6. 检查是否存在阻塞会话、I/O 延迟或并发突增。

如果同一 SQL 的 ASH 样本主要是 ON CPU,应检查逻辑读、连接条件、排序、聚合和执行次数;如果主要是 User I/O,应继续区分物理读、读请求延迟和 SQL 是否不必要地访问了大量数据;如果主要是 ConcurrencyApplication,则应检查锁、闩锁、事务和共享资源。


四、等待事件:等待什么不等于谁有错

1. 等待事件的结构

一个等待事件通常可以理解为:

会话
 ├─ 状态:WAITING 或 ON CPU
 ├─ 事件:等待的资源或同步点
 ├─ 等待类别:User I/O、Commit、Concurrency 等
 ├─ 参数:例如文件号、块号、对象号、锁类型
 └─ 持续时间与等待次数

在当前会话视图中,可以先观察活动会话:

SELECT sid,
       serial#,
       username,
       status,
       sql_id,
       event,
       wait_class,
       state,
       seconds_in_wait,
       blocking_session,
       blocking_session_status
FROM   v$session
WHERE  status = 'ACTIVE'
ORDER BY seconds_in_wait DESC;

常见字段的含义:

  • EVENT:当前等待事件;
  • WAIT_CLASS:等待类别;
  • STATE:当前是等待、刚结束等待,还是其他状态;
  • SECONDS_IN_WAIT:当前等待相关的时间字段;
  • BLOCKING_SESSION:若已识别阻塞者,给出阻塞会话;
  • SQL_ID:当前或相关 SQL 的标识。

V$SESSION 是实时视图,适合回答“现在发生了什么”,不适合单独回答“过去一小时发生了什么”。

2. 常见等待事件的正确解释

db file sequential read

通常表示一次单块数据文件读取。它经常出现在索引访问、按 ROWID 回表或其他单块读取路径中,但事件名称不能证明“索引有问题”。

可能原因包括:

  • 索引选择性差;
  • 大量索引扫描后回表;
  • 缓存命中不足;
  • 随机访问模式;
  • SQL 估算错误导致错误的访问路径。

反例是:某 SQL 使用了索引,但返回了表中大部分行。此时索引并不一定比全表扫描好,单块读等待反而可能增加。

db file scattered read

在传统命名下常与多块读取相关,常见于全表扫描或全索引扫描。但具体行为取决于版本、访问路径和执行环境。不能仅凭事件名称认定“全表扫描就是错误”,因为对大部分数据的顺序读取可能比索引回表更合理。

direct path read

表示绕过部分缓冲区缓存路径的直接读取,可能与大表扫描、临时段读取或其他执行方式有关。需要结合 SQL 计划、对象大小、并行度和 I/O 延迟判断。

log file sync

前台会话提交时等待 LGWR 等日志写入路径完成。常见原因包括:

  • 提交过于频繁;
  • redo 生成量大;
  • 日志写入延迟;
  • 存储或虚拟化层抖动;
  • LGWR 受到 CPU 调度影响。

“出现 log file sync 就应该关闭日志”是错误做法。日志持久性是事务提交语义的重要组成部分,不能为了降低等待而破坏提交可靠性。

enq: TX - row lock contention

通常表示事务正在等待其他事务释放行锁或相关事务资源。诊断时要找到阻塞链:

SELECT sid,
       serial#,
       username,
       event,
       blocking_session,
       sql_id,
       row_wait_obj#
FROM   v$session
WHERE  blocking_session IS NOT NULL;

还应结合事务持续时间、应用请求、未提交事务和锁对象进一步确认。直接杀掉阻塞会话可能导致回滚,回滚时间又可能继续阻塞其他会话。

buffer busy waits

通常说明多个会话对某个缓冲区存在并发访问冲突,但根因可能是热点块、段头、索引叶块、块管理方式或工作负载模式。不能看到该事件就直接增加缓冲区缓存。

cursor: pin S wait on X 和共享游标相关等待

这类等待可能与游标失效、硬解析、共享池竞争、DDL 或对象状态变化有关。需要结合 SQL 版本数量、解析次数、应用是否使用绑定变量以及具体版本行为分析。

3. 等待类别是分类,不是根因

等待类别便于排序和归纳,例如:

  • User I/O:用户 SQL 直接相关的 I/O 等待;
  • System I/O:后台进程或系统级 I/O;
  • Commit:提交路径;
  • Concurrency:并发控制;
  • Application:应用级同步或锁;
  • Configuration:配置导致的等待;
  • Network:网络相关;
  • Scheduler:调度或资源管理相关。

类别只能缩小范围。真正的诊断需要把:

等待事件
+ 等待参数
+ SQL
+ 执行计划
+ 对象
+ 阻塞者
+ 主机资源
+ 时间窗口

联系起来。


五、统计信息:优化器对数据分布的模型

1. 统计信息不是运行时计数器

Oracle 优化器需要估算候选执行计划的成本。它使用的统计信息通常包括:

  • 表的行数和块数;
  • 列的最小值、最大值、非空数量;
  • 列的不同值数量;
  • 直方图;
  • 索引的层级、叶块、聚簇因子等;
  • 分区级或全局统计信息;
  • 相关的系统统计信息。

统计信息描述的是优化器看到的数据模型,不是“这条 SQL 最近运行了多少次”。

这一区分很重要:

  • DBA_TAB_STATISTICS 等视图描述对象统计信息;
  • AWR、ASH、动态性能视图描述运行时活动;
  • 执行计划描述优化器选择的访问路径;
  • 运行时执行统计描述实际行数和资源消耗。

2. 优化器为何会选错计划

设某个谓词选择率为 ss,表中行数为 NN,优化器估算返回行数:

R^=N×s\hat{R} = N \times s

如果真实返回行数为 RR,则估算误差可以用:

E=RR^E = \frac{R}{\hat{R}}

表示。

例如:

  • 表行数 N=10,000,000N = 10,000,000
  • 优化器估算选择率 s=0.001s = 0.001
  • 估算返回行数 R^=10,000\hat{R} = 10,000
  • 实际返回行数 R=2,000,000R = 2,000,000

则:

E=2,000,00010,000=200E = \frac{2,000,000}{10,000}=200

优化器以为只需要处理 1 万行,实际要处理 200 万行。它可能选择索引加回表,而实际全表扫描或其他批量访问路径更合适。

这就是为什么执行计划调优不能只盯着“是否使用索引”。关键问题是:

  1. 基数估算是否接近真实值;
  2. 访问路径是否匹配返回数据量;
  3. 连接顺序和连接方式是否合理;
  4. 排序、聚合、临时空间和并行度是否合适。

3. 直方图解决什么问题

如果列值分布均匀,优化器可用不同值数量近似估算选择率。但现实中可能存在倾斜:

status = 'ACTIVE'   99%
status = 'DELETED'   1%

如果没有足够的分布信息,优化器可能对两个值使用相近的选择率估计。直方图用于表达列值分布,使不同谓词得到不同估算。

但直方图不是越多越好:

  • 它增加统计信息维护复杂度;
  • 数据分布变化后可能过期;
  • 绑定变量和谓词值的变化可能导致不同计划;
  • 重新收集统计信息可能触发计划变化。

可以查看表和列统计信息:

SELECT owner,
       table_name,
       num_rows,
       blocks,
       last_analyzed,
       stale_stats
FROM   dba_tab_statistics
WHERE  owner = 'APP'
AND    table_name = 'ORDERS';

SELECT owner,
       table_name,
       column_name,
       num_distinct,
       num_nulls,
       histogram,
       last_analyzed
FROM   dba_tab_col_statistics
WHERE  owner = 'APP'
AND    table_name = 'ORDERS';

NUM_ROWSBLOCKS 是统计信息中的估计值,不应直接当作实时精确值。

4. 收集统计信息

典型示例:

BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(
      ownname          => 'APP',
      tabname          => 'ORDERS',
      estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
      method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
      cascade          => DBMS_STATS.AUTO_CASCADE,
      no_invalidate    => DBMS_STATS.AUTO_INVALIDATE
  );
END;
/

这些参数的意图是:

  • AUTO_SAMPLE_SIZE:让 Oracle 选择采样策略;
  • SIZE AUTO:根据需要决定直方图;
  • AUTO_CASCADE:由 Oracle 决定是否处理相关索引统计;
  • AUTO_INVALIDATE:由 Oracle 决定游标失效策略。

但“自动”不等于不会产生风险。收集统计信息可能导致:

  • 优化器重新选择计划;
  • 硬解析或游标重新验证;
  • 统计信息写入占用 CPU、I/O 和临时空间;
  • 大表收集时间较长;
  • 业务高峰期间资源竞争。

因此应在业务低峰执行,并在执行前后保存:

  • SQL ID;
  • 执行计划;
  • SQL 资源指标;
  • 统计信息时间;
  • 业务响应时间。

如果需要恢复,Oracle 提供统计信息历史能力和相应的恢复接口,但前提是历史尚未过期且权限、保留策略和对象范围都满足要求。恢复统计信息之前,应确认问题确实由统计信息变化引起,而不是把其他变化覆盖掉。

5. 动态采样、SQL Plan Management 和 Hint 的边界

当持久化统计不足时,优化器可能使用动态采样或其他自适应机制改善估算。它不是统计信息维护的替代品:

  • 动态采样有额外优化阶段开销;
  • 复杂数据分布仍可能估算错误;
  • 运行时数据变化可能使一次采样不具代表性。

Hint 也不是永久修复手段。Hint 直接影响某次 SQL 的优化决策,但可能因:

  • 表结构变化;
  • 数据分布变化;
  • 版本变化;
  • Hint 无效或被忽略;
  • SQL 文本或别名变化;

而失去预期效果。

生产调优应优先修正 SQL 语义、数据访问方式和统计信息;需要稳定计划时,再评估 SQL Plan Management、SQL Profile、基线或有限范围的 Hint。不同功能涉及不同许可和版本能力,不能混为一谈。


六、从统计信息回到实际执行计划

1. EXPLAIN PLAN 不等于实际执行结果

以下命令展示的是优化器为某次解析生成的计划:

EXPLAIN PLAN FOR
SELECT o.order_id
FROM   app.orders o
WHERE  o.customer_id = 1001;

SELECT *
FROM   TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +PREDICATE'));

它有两个限制:

  1. 它可能不是应用实际执行的游标;
  2. 它没有真实执行行数和真实资源消耗。

更可靠的方式是查看已经执行的游标:

SELECT *
FROM   TABLE(
  DBMS_XPLAN.DISPLAY_CURSOR(
      sql_id          => '9abc123def456',
      format          => 'ALLSTATS LAST +PEEKED_BINDS +PREDICATE'
  )
);

要看到实际行数,执行时通常需要采集执行统计,例如:

SELECT /*+ GATHER_PLAN_STATISTICS */
       o.order_id
FROM   app.orders o
WHERE  o.customer_id = 1001;

也可以使用适当的会话或系统级统计配置,但范围越大,额外开销越高。

2. 重点看估算行数和实际行数

执行计划中常见两组重要列:

  • E-Rows:优化器估算行数;
  • A-Rows:实际执行行数。

假设某连接节点显示:

Operation              E-Rows        A-Rows
HASH JOIN              1000          2000000
TABLE ACCESS           1000          2000000

这说明估算误差约为 2000 倍。此时应优先检查:

  • 谓词列统计信息;
  • 数据倾斜和直方图;
  • 多列相关性;
  • 隐式类型转换;
  • 绑定变量行为;
  • 分区裁剪是否发生;
  • SQL 条件是否与业务实际选择性一致。

如果 E-RowsA-Rows 接近,但 SQL 仍慢,问题可能在:

  • 单次访问量本身太大;
  • I/O 延迟;
  • CPU 资源不足;
  • 并发争用;
  • 排序或临时空间;
  • 执行次数过多。

3. 估算正确也可能计划不适合

反例:

  • 估算返回 100 万行;
  • 实际也返回 100 万行;
  • 但 SQL 只需要 10 个字段中的 2 个字段;
  • 当前计划通过低选择性索引逐行回表。

这时统计信息可能是正确的,问题是访问路径的成本模型和物理设计不适合工作负载。可以评估:

  • 更合理的索引前导列;
  • 覆盖索引是否值得;
  • 全表扫描或分区扫描;
  • SQL 是否可以减少返回列和行;
  • 是否存在不必要的排序、去重或函数计算。

因此“更新统计信息”不能成为所有计划问题的统一答案。


七、从 AWR、ASH 和执行计划建立因果链

一个可靠的调优结论应至少形成如下链路:

业务症状
  ↓
时间窗口和影响范围
  ↓
AWR:区间负载、DB time、CPU、等待类别
  ↓
ASH:具体 SQL、会话、阻塞者和时间分布
  ↓
执行计划:访问路径、连接顺序、E-Rows/A-Rows
  ↓
统计信息、对象结构和主机资源
  ↓
修复措施与回归验证

示例:接口从 200 ms 变为 5 秒

第一步:确认数据库是否真的变慢

查看该时间窗口的:

  • 数据库时间;
  • 前台等待时间;
  • 事务提交时间;
  • CPU;
  • 用户 I/O;
  • 活跃会话数量;
  • SQL 执行次数。

如果应用响应时间增加,但数据库时间没有增加,应优先检查连接池、网络或应用线程池,而不是直接改 SQL。

第二步:用 ASH 定位活动来源

如果 ASH 显示某 SQL 的样本突然增多,并且主要等待 User I/O,可以进一步查询:

  • 该 SQL 的执行次数是否突增;
  • 单次逻辑读和物理读是否增加;
  • 是否出现新的子游标;
  • 是否发生计划变化;
  • 对象是否刚刚装载大量数据。

第三步:比较实际计划

使用 DBMS_XPLAN.DISPLAY_CURSOR 比较正常时和异常时的:

  • 计划哈希值;
  • E-RowsA-Rows
  • 连接顺序;
  • 索引、全表扫描和分区裁剪;
  • 临时空间;
  • 并行执行;
  • 绑定变量窥视结果。

第四步:判断根因

可能得到不同结论:

  • 统计信息过期,导致基数估算错误;
  • 数据倾斜变化,原有直方图不再代表现实;
  • SQL 文本未变但子游标因环境或绑定变量产生不同计划;
  • 业务调用次数增加十倍,单次执行没有变慢;
  • 存储延迟增加,SQL 计划没有变化;
  • 事务未提交,后续请求在等待行锁。

每一种结论的修复方式都不同。


八、容量分析:从“现在很慢”推导“还能承受多少”

1. 容量不是单一指标

数据库容量至少包含:

  • CPU 容量;
  • I/O 吞吐和 I/O 延迟容量;
  • 内存容量;
  • 并发会话容量;
  • 日志写入容量;
  • 锁和事务并发容量;
  • 临时空间容量;
  • 表空间和归档空间容量;
  • 连接池及网络容量。

“CPU 还有 30%”不能证明系统还有 30% 的整体容量,因为 I/O、日志、锁或连接数可能已经先达到瓶颈。

2. Little 定律与活动会话

排队系统中常用 Little 定律:

L=λRL = \lambda R

其中:

  • LL:系统中的平均请求数;
  • λ\lambda:吞吐率;
  • RR:平均响应时间。

在数据库分析中,可以近似理解为:

AASTPS×每事务平均数据库时间AAS \approx TPS \times \text{每事务平均数据库时间}

例如:

  • 每秒 200 个事务;
  • 每个事务平均消耗 20 ms 数据库时间。

则:

AAS=200×0.02=4AAS = 200 \times 0.02 = 4

这表示平均有约 4 个活动会话。

如果吞吐量不变,而单事务数据库时间从 20 ms 增加到 100 ms:

AAS=200×0.1=20AAS = 200 \times 0.1 = 20

活动会话增加五倍,连接池、锁竞争和 CPU 争用可能随之恶化。性能问题常常不是线性增长:接近资源上限后,排队时间会快速增加。

3. 用服务需求估算资源上限

设某类请求每秒到达 λ\lambda 次,每次在某资源上平均消耗 DD 秒,则该资源所需利用率近似为:

U=λDU = \lambda D

例如,某 SQL 每秒执行 100 次,每次平均消耗 8 ms CPU:

UCPU=100×0.008=0.8U_{\text{CPU}} = 100 \times 0.008 = 0.8

这相当于消耗 0.8 个 CPU 核。如果数据库可用 CPU 配额只有 1 个核,CPU 已接近饱和;如果可用 8 个核,CPU 可能不是主要瓶颈。

对 I/O 也可以使用类似思路。若每秒产生 500 次 I/O 请求,每次存储服务时间平均 4 ms:

UI/O=500×0.004=2U_{\text{I/O}} = 500 \times 0.004 = 2

这意味着单一串行服务能力不足,实际系统可能依赖多个并行服务队列;不能简单把这个结果当作设备利用率,但它说明“请求率乘以单次服务时间”已经超过单服务通道能力。

4. 不能用平均值掩盖峰值

容量模型应至少分别计算:

  • 工作日平均;
  • 业务高峰;
  • 批处理窗口;
  • 发布或数据装载窗口;
  • 故障降级场景;
  • 副本、备份和统计信息收集期间。

例如某小时平均 TPS 为 100,但五分钟峰值 TPS 为 500。用 100 TPS 规划 CPU 和 I/O,可能在高峰时直接进入排队区。

AWR 适合观察区间总量和趋势,ASH 适合定位尖峰中的活动会话,操作系统监控则用于验证主机级 CPU、内存、I/O 和网络。


九、表空间、临时空间和日志容量

1. 表空间容量

查看永久表空间使用情况时,应明确使用的是数据文件总大小、已使用空间还是可自动扩展上限。不同查询反映不同问题。

例如可以先查看数据文件:

SELECT tablespace_name,
       file_name,
       bytes / 1024 / 1024 AS size_mb,
       autoextensible,
       maxbytes / 1024 / 1024 AS max_mb
FROM   dba_data_files
ORDER BY tablespace_name, file_name;

这里的 MAXBYTES 只是数据文件的自动扩展上限,不代表底层文件系统或 ASM 磁盘组真的有足够空间。

因此容量判断至少要同时验证:

  • 表空间剩余可用空间;
  • 数据文件是否允许自动扩展;
  • 自动扩展上限;
  • 文件系统或 ASM 磁盘组剩余空间;
  • 增长速度;
  • 大对象、分区和索引的增长来源。

“表空间还有 20%”也不一定安全。如果每天增长 50 GB,而底层只剩 10 GB,系统仍会很快失败。

2. 临时表空间

排序、哈希连接、临时结果集和某些并行操作可能使用临时表空间。检查临时文件:

SELECT tablespace_name,
       file_name,
       bytes / 1024 / 1024 AS size_mb,
       autoextensible,
       maxbytes / 1024 / 1024 AS max_mb
FROM   dba_temp_files
ORDER BY tablespace_name, file_name;

临时空间增长的诊断路径包括:

  1. 观察临时空间当前使用量;
  2. 找到消耗临时空间的会话和 SQL;
  3. 查看执行计划中的排序、哈希和并行操作;
  4. 判断是数据量增长、内存不足、计划变化还是 SQL 逻辑问题;
  5. 扩容前确认底层空间和增长上限。

单纯扩大临时表空间只能延后失败,不能修复错误连接顺序或不必要排序。

3. 归档和日志容量

日志相关容量包括:

  • redo 日志组;
  • 归档日志空间;
  • 备份保留;
  • Data Guard 传输或应用延迟;
  • 恢复窗口要求。

如果归档目的地空间耗尽,数据库可能出现严重影响甚至无法继续正常归档。容量监控必须观察增长速率和故障恢复路径,而不是只设置一个固定百分比阈值。


十、锁、事务和等待链

等待事件经常只是阻塞链的表象。一个完整的锁问题通常是:

会话 A 开启事务并修改行
        │
        │ 未提交
        ▼
会话 B 修改同一行
        │
        ▼
enq: TX - row lock contention
        │
        ▼
会话 C 可能继续等待 B 或其他资源

诊断时应同时观察:

  • 阻塞会话;
  • 被阻塞会话;
  • 事务开始时间;
  • 当前 SQL;
  • 最后一次提交时间;
  • 应用请求和连接归属;
  • 回滚段和事务持续时间。

不能把所有锁等待归因于“数据库锁配置不合理”。很多锁问题来自应用事务边界:

  • 开启事务后执行远程调用;
  • 修改数据后等待用户操作;
  • 连接归还池前未提交或回滚;
  • 批量操作过大;
  • 异常路径没有正确结束事务。

修复阻塞的优先级通常是先停止或修正制造长事务的业务路径,再评估是否需要终止会话。终止会话不是无成本操作,可能触发大量回滚,并使恢复时间不可预测。


十一、常见误解与失败诊断

误解一:Top Wait Event 第一名就是根因

某等待事件累计时间最多,可能只是因为执行次数特别多。应同时看:

平均等待时间=总等待时间等待次数\text{平均等待时间} = \frac{\text{总等待时间}}{\text{等待次数}}

例如:

  • 事件 A:等待 100 万次,总计 1000 秒,平均 1 ms;
  • 事件 B:等待 1000 次,总计 500 秒,平均 500 ms。

事件 A 的累计时间更高,但事件 B 可能更直接反映存储或锁的异常。

误解二:出现物理读就应该增加内存

物理读可能由以下原因造成:

  • SQL 扫描了过多数据;
  • 索引回表次数过多;
  • 工作集本来就超过内存;
  • 缓存被大量无效访问冲刷;
  • 存储延迟异常。

增加缓存只能缓解部分问题。如果 SQL 每次都扫描一个远大于缓存的表,缓存增加不一定有效。

误解三:执行计划用了索引就一定更快

索引访问适合高选择性、访问列和数据布局匹配的场景。返回大量行时,索引逐行回表可能比顺序扫描更慢。

判断依据应是:

  • 返回行数;
  • E-RowsA-Rows
  • 逻辑读和物理读;
  • 单次执行时间;
  • 执行次数;
  • 并发下的总资源消耗。

误解四:重新收集统计信息一定能修复慢 SQL

如果真正根因是:

  • 锁等待;
  • 存储延迟;
  • 提交过于频繁;
  • SQL 执行次数暴增;
  • 应用连接池排队;
  • 主机 CPU 配额不足;

重新收集统计信息不仅无效,还可能改变原本稳定的执行计划。

误解五:ASH 没有记录就说明没有问题

ASH 是采样系统。短 SQL、瞬时锁和采样间隔之间的事件可能漏掉。对短时尖峰,应结合:

  • 应用日志;
  • 实时 V$ 视图;
  • SQL 监控能力;
  • 操作系统监控;
  • 业务时间戳;
  • 数据库审计或专门追踪机制。

不能把 ASH 当作完整事件日志。

误解六:EXPLAIN PLAN 就是应用实际运行的计划

实际游标可能因以下原因不同:

  • 绑定变量不同;
  • 子游标不同;
  • 会话环境不同;
  • 统计信息变化;
  • SQL Plan Management;
  • 自适应游标行为;
  • 对象或分区状态变化。

调优时应优先检查实际游标和实际执行统计。


十二、一个可重复的生产诊断流程

第一步:固定事实边界

记录:

  • 数据库版本和补丁;
  • 单实例还是 RAC;
  • 数据库时间、主机时间和应用时间是否一致;
  • 问题开始和结束时间;
  • 受影响的接口、用户和 SQL;
  • 是否发生发布、统计信息收集、批处理、备份或数据装载。

时间边界错误会导致 AWR、ASH 和应用日志互相对不上。

第二步:判断是数据库整体问题还是局部 SQL 问题

先看:

  • DB time 是否上升;
  • AAS 是否上升;
  • DB CPU 是否上升;
  • 等待类别是否改变;
  • 并发请求是否改变。

若只有一个 SQL 变慢,重点转向执行计划、统计信息和对象访问。若大量 SQL 同时变慢,重点检查 CPU、I/O、日志、锁、网络、存储和资源管理。

第三步:用 AWR 看区间差异

比较正常区间与异常区间:

  • 业务吞吐是否变化;
  • 每秒逻辑读、物理读、redo 和事务数;
  • DB time 每秒;
  • DB CPU 每秒;
  • 主要等待事件;
  • SQL 排名变化;
  • 主机资源变化。

必须看“每秒”或“每次执行”的归一化指标,不能只看总量。

第四步:用 ASH 定位并发来源

观察:

  • 哪些 SQL 占用活动样本最多;
  • 哪些模块或服务产生活动;
  • 是 CPU 还是等待;
  • 是否形成阻塞链;
  • 问题是否集中在某个实例;
  • 是否存在计划切换。

第五步:检查执行计划和统计信息

对候选 SQL:

  1. 找到实际 SQL_ID
  2. 查看子游标;
  3. 查看实际执行计划;
  4. 对比 E-RowsA-Rows
  5. 查看谓词是否发生隐式转换;
  6. 检查表、列、索引统计信息;
  7. 核对分区裁剪和连接条件;
  8. 评估修复后的回归风险。

第六步:验证修复而不是只验证命令成功

修复后的验证应包括:

  • 同样时间窗口的 AAS;
  • 单次执行 DB time;
  • CPU、逻辑读、物理读;
  • 等待事件和平均等待时间;
  • 执行计划是否符合预期;
  • 业务响应时间;
  • 并发高峰表现;
  • 回滚或恢复方案是否可用。

“统计信息收集成功”“索引创建成功”只说明操作完成,不说明性能问题已经解决。


十三、监控查询示例:把指标放到同一张图上

下面的查询用于查看某段时间内 ASH 按 SQL 聚合的活动样本:

SELECT sql_id,
       COUNT(*) AS active_samples,
       SUM(CASE WHEN session_state = 'ON CPU' THEN 1 ELSE 0 END)
         AS on_cpu_samples,
       SUM(CASE WHEN session_state = 'WAITING' THEN 1 ELSE 0 END)
         AS waiting_samples
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '30' MINUTE
GROUP BY sql_id
ORDER BY active_samples DESC;

解释:

  • active_samples:该 SQL 出现在活动会话采样中的次数;
  • on_cpu_samples:近似反映 CPU 活动;
  • waiting_samples:近似反映等待活动;
  • 这些值不是精确执行次数,也不是精确等待调用次数。

若要查看当前系统级等待:

SELECT event,
       wait_class,
       total_waits,
       time_waited_micro / 1000000 AS time_waited_seconds
FROM   v$system_event
WHERE  wait_class <> 'Idle'
ORDER BY time_waited_micro DESC;

系统累计视图必须结合启动时间或快照差分使用。数据库运行了很久时,累计总量很大并不等于最近发生了异常。

查看数据库启动时间:

SELECT startup_time
FROM   v$instance;

如果没有 AWR,也可以在两个时间点采集动态性能视图并自行计算增量,但这不等同于 AWR 的完整历史能力,且采集过程本身需要设计好权限、频率和存储。


十四、监控系统应如何保存指标

生产监控不应只保存一个“数据库 CPU 百分比”。至少需要保存以下维度:

数据库层

  • DB time;
  • DB CPU;
  • AAS;
  • 每秒执行次数;
  • 每次执行逻辑读、物理读、CPU 时间;
  • 提交和回滚次数;
  • redo 生成速率;
  • 等待类别和主要等待事件;
  • 阻塞会话数量;
  • 长事务;
  • 临时空间使用;
  • 表空间和归档空间增长。

SQL 层

  • SQL_ID
  • 计划哈希值;
  • 执行次数;
  • 平均和分位响应时间;
  • CPU 时间;
  • DB time;
  • 逻辑读和物理读;
  • 返回行数;
  • 子游标数量;
  • 计划切换时间。

主机和存储层

  • CPU 使用率和运行队列;
  • 可用内存和交换;
  • IOPS;
  • 吞吐;
  • I/O 延迟;
  • 文件系统或 ASM 容量;
  • 网络延迟和丢包;
  • 虚拟机或容器 CPU、I/O 配额。

指标必须带有时间窗口、实例、服务、模块和环境标签。否则同一个 SQL 在不同实例、不同服务和不同业务路径下的行为会被混在一起。


十五、生产取舍:观测能力本身也有成本

AWR、ASH、执行统计和追踪会消耗:

  • CPU;
  • 内存;
  • I/O;
  • 数据字典空间;
  • 日志和报告存储;
  • 运维人员的分析时间。

因此应区分三种场景:

日常监控

使用低开销的系统指标、应用指标、连接和事务指标,发现趋势和 SLO 违约。

问题定位

在确定时间窗口内使用 AWR、ASH、实时视图和实际执行计划,减少全库范围的长期高成本采集。

深度实验

在可回滚、可隔离的环境中启用更详细的执行统计或 SQL 追踪,验证假设后再推广到生产。

调优不是把所有诊断开关永久打开,而是在足够观测和可接受开销之间取得平衡。


Oracle 性能分析的核心不是记住某个等待事件对应某个参数,而是建立可验证的因果关系:

业务吞吐数据库时间CPU 与等待具体 SQL 和会话执行计划与统计信息资源容量和并发边界\text{业务吞吐} \rightarrow \text{数据库时间} \rightarrow \text{CPU 与等待} \rightarrow \text{具体 SQL 和会话} \rightarrow \text{执行计划与统计信息} \rightarrow \text{资源容量和并发边界}

AWR 提供区间视角,ASH 提供活动会话视角,等待事件提供资源等待视角,统计信息和执行计划提供优化器视角,容量模型则把这些结果转化为对峰值、增长和故障余量的判断。只有把这些视角放在同一条时间线上,调优结论才不会停留在“看到一个事件就修改一个参数”。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论

0 条讨论
0/1000
还没有评论,来聊聊你的看法
WR Blog 加载中...
返回文章
数据库OracleAWR性能优化

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量封面

数据库基础体系 · 第 112/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量

Oracle 性能问题通常不是“某条 SQL 慢”这么简单。一次请求从客户端到数据库,可能经历连接排队、CPU 争用、逻辑读、物理读、锁等待、日志提交、并行执行、网络传输和存储响应。要判断问题,必须同时回答四个问题:

  1. 数据库当时在做什么?
  2. 时间花在哪里?
  3. 优化器为什么选择这个执行计划?
  4. 当前资源是否已经接近容量边界?

Oracle 提供了几组互补的观测机制:

  • AWR(Automatic Workload Repository):以快照为边界保存一段时间内的数据库工作负载摘要。
  • ASH(Active Session History):以活动会话为对象,记录会话在某个时间点正在运行或等待什么。
  • 等待事件(Wait Events):描述会话为什么没有继续执行。
  • 统计信息(Optimizer Statistics):描述表、列、索引等对象的数据分布,供优化器估算执行代价。
  • 容量分析:把吞吐量、响应时间、CPU、I/O、并发和数据库时间联系起来,判断系统距离瓶颈还有多少余量。

这些机制的观察对象不同。AWR 和 ASH 不能替代执行计划,等待事件也不能直接证明某个对象有问题,统计信息更不是运行时性能计数器。


一、先区分几种“时间”:响应时间、数据库时间和资源时间

1. 响应时间不等于数据库时间

一次请求的响应时间可以抽象为:

R=Rqueue+Rapplication+Rnetwork+RdatabaseR = R_{\text{queue}} + R_{\text{application}} + R_{\text{network}} + R_{\text{database}}

其中:

  • RR:用户感知的端到端响应时间;
  • RqueueR_{\text{queue}}:请求在连接池、线程池或其他队列中的等待时间;
  • RapplicationR_{\text{application}}:应用代码执行时间;
  • RnetworkR_{\text{network}}:网络传输时间;
  • RdatabaseR_{\text{database}}:数据库内部处理时间。

Oracle 主要观察数据库内部部分。即使 AWR 显示数据库负载正常,应用仍可能因为连接池耗尽或网络延迟而超时。

Oracle 中常见的 DB time 是所有前台会话处于活动状态时消耗的时间总和。活动状态通常包括:

  • 正在使用 CPU;
  • 正在等待非空闲等待事件。

如果同一时刻有 10 个会话各自活动 1 秒,DB time 大约增加 10 秒,而墙上时钟只过去 1 秒。因此:

平均活动会话数(AAS)=DB time墙上时间\text{平均活动会话数(AAS)} = \frac{\text{DB time}}{\text{墙上时间}}

AAS 是性能分析中的核心量。它回答的是:在一个时间区间内,平均有多少个前台会话正在消耗数据库资源或等待数据库资源。

2. 一个完整算例

假设某小时的 AWR 报告给出:

  • DB time:7200 秒;
  • DB CPU:3600 秒;
  • 非空闲等待时间约为 3600 秒;
  • 该区间长度:3600 秒。

则:

AAS=72003600=2AAS = \frac{7200}{3600} = 2

平均有 2 个前台会话处于活动状态。

进一步可以分解:

DB timeDB CPU+非空闲等待时间\text{DB time} \approx \text{DB CPU} + \text{非空闲等待时间}

于是:

  • CPU 占 DB time:3600/7200=50%3600 / 7200 = 50\%
  • 非空闲等待占 DB time:3600/7200=50%3600 / 7200 = 50\%

这并不表示数据库只用了 50% 的主机 CPU。DB CPU 是所有会话累计使用的 CPU 时间,而主机 CPU 使用率还受到后台进程、其他进程、CPU 核数和虚拟化调度的影响。

如果数据库运行在 8 个逻辑 CPU 上,那么 3600 秒 DB CPU 分布在 3600 秒墙上时间中,平均相当于约 1 个 CPU 核:

平均 CPU 核占用=DB CPU墙上时间=1\text{平均 CPU 核占用} = \frac{\text{DB CPU}}{\text{墙上时间}} = 1

但这仍然不能直接推出“CPU 没有问题”。如果业务高峰只有 5 分钟,峰值 CPU 可能远高于小时平均值;如果运行在受限的容器 CPU 配额中,8 个宿主机 CPU 也不代表数据库可以使用 8 个 CPU。

3. CPU 不是等待事件

Oracle 会话状态常见为:

  • ON CPU:正在 CPU 上执行;
  • WAITING:正在等待某个事件;
  • 其他状态:例如会话未处于数据库活动执行阶段。

CPU 消耗不会以一个叫作 CPU 的等待事件出现。分析时必须同时看:

  • DB CPU;
  • 前台等待时间;
  • 活跃会话数量;
  • 主机 CPU、运行队列和 CPU 配额;
  • SQL 的执行次数和单次资源消耗。

“没有明显等待事件”不代表没有性能问题,可能是 CPU 饱和、SQL 做了过多逻辑读,或者应用端没有把数据库时间记录完整。


二、AWR:以快照比较工作负载变化

1. AWR 记录什么

AWR 是 Oracle 自动工作负载资料库。它周期性记录数据库状态和工作负载摘要,典型内容包括:

  • 系统级时间模型统计;
  • 系统等待事件;
  • 前台和后台统计;
  • SQL 的执行次数、逻辑读、物理读、CPU 时间、数据库时间等;
  • 实例活动和部分资源统计;
  • 对象级访问或变更统计;
  • 相关的优化器和系统信息。

AWR 的基本数据流是:

运行中的内存统计
        │
        │ 生成快照
        ▼
AWR 持久化历史数据
        │
        │ 比较两个快照
        ▼
区间报告:增量、排名、趋势

AWR 不是实时监控工具。它记录的是快照之间的累计变化,因此一个小时的报告可能掩盖五分钟内发生的尖峰。

AWR 的诊断数据通常保存在数据库内部的相关表空间中。快照具有:

  • 快照编号;
  • 开始时间;
  • 结束时间;
  • 实例标识;
  • 数据库标识。

在 RAC 环境中,需要注意数据库级和实例级视角,查询通常需要使用 GV$ 视图或在正确的实例上生成报告。

2. 快照间隔和保留时间不是性能结论

Oracle 环境中的快照间隔和保留时间可以配置。某些安装的常见默认值是每小时生成一次并保留若干天,但具体值取决于版本、安装方式和管理员配置,不能把默认值当作规范保证。

可以通过以下查询查看配置:

SELECT snap_interval,
       retention,
       topnsql
FROM   dba_hist_wr_control;

这些字段分别描述快照间隔、保留策略和 SQL 记录范围。查询通常需要相应的数据字典权限。

在生产环境中,修改 AWR 控制参数会影响存储量和诊断能力,不应为了“让报告更详细”而无限缩短间隔或无限延长保留时间。更高的采样或保留成本需要结合问题发生频率、存储空间和合规要求评估。

3. AWR 报告的本质是“两个状态的差分”

例如,某 SQL 在快照 100 时累计:

  • 执行次数:1000;
  • DB time:500 秒;
  • 逻辑读:1,000,000。

在快照 101 时累计:

  • 执行次数:1100;
  • DB time:900 秒;
  • 逻辑读:1,800,000。

那么区间增量为:

  • 执行次数:100;
  • DB time:400 秒;
  • 逻辑读:800,000。

区间平均值为:

平均每次执行 DB time=400100=4 秒\text{平均每次执行 DB time} = \frac{400}{100} = 4\text{ 秒}

平均每次执行逻辑读=800000100=8000 次\text{平均每次执行逻辑读} = \frac{800000}{100} = 8000\text{ 次}

不能直接用累计值判断当前区间性能,否则会把数据库启动以来的历史工作负载混入结论。

4. 生成 AWR 报告

Oracle 常见环境中可以使用 SQL*Plus 提供的报告脚本:

@?/rdbms/admin/awrrpt.sql

脚本通常会依次询问:

  1. 报告类型,例如单实例或 RAC;
  2. 报告格式;
  3. 起始快照;
  4. 结束快照;
  5. 输出文件名。

它要求执行者具有足够的访问权限,并且环境必须具备相应的 AWR 能力。

报告结果应按以下顺序阅读:

  1. 报告时间范围和数据库实例;
  2. Load Profile;
  3. Top Timed Events;
  4. Time Model Statistics;
  5. SQL 统计;
  6. Instance Activity;
  7. 主机 CPU 和 I/O 信息;
  8. 与业务峰值、发布、批处理时间对照。

不要看到 Top Timed Events 的第一项就立刻修改参数。它只说明某个事件在该时间区间累计消耗较多时间,还需要结合执行次数、并发量、调用链和资源上限。

5. AWR 的许可证边界

AWR、ASH 以及相关历史诊断能力属于 Oracle Diagnostics Pack 的范围。是否可以在某个环境中使用,取决于 Oracle 版本、版本选项、部署方式和许可证合同。不能因为视图存在就默认可以在任何环境中自由使用。

在生产环境使用 AWR 报告、历史 ASH 或相关接口前,应由负责 Oracle 许可证的团队确认授权范围。本文讨论的是功能语义,不替代许可证判断。


三、ASH:回答“某一时刻哪些会话在做什么”

1. ASH 的观察单位是活动会话

ASH,即 Active Session History,面向“活动会话”采样。一个样本通常包含类似信息:

  • 样本时间;
  • 会话标识;
  • SQL 标识;
  • 会话当前是否在 CPU 上;
  • 等待事件和等待参数;
  • 等待类别;
  • 当前对象或数据文件等上下文;
  • 阻塞会话相关信息;
  • 服务名、模块、动作等会话属性。

ASH 的重要特点是:

  • 它不是每次等待开始和结束都完整记录;
  • 它主要记录活动会话;
  • 它通常以约一秒级的采样粒度观察活动;
  • 短于采样间隔的瞬时事件可能被漏采;
  • 长时间持续的等待更容易被观察到。

因此,ASH 是抽样历史,不是完整审计日志。某条 SQL 没出现在 ASH 中,不一定代表它从未执行过,可能是执行太快、未处于活动状态,或者历史数据已被覆盖。

2. 当前 ASH 与历史 ASH

当前内存中的活动会话历史可通过 V$ACTIVE_SESSION_HISTORY 查询。历史持久化数据通常通过 DBA_HIST_ACTIVE_SESS_HISTORY 查询。

一个查看最近活动会话的示例:

SELECT sample_time,
       session_id,
       session_serial#,
       session_state,
       event,
       wait_class,
       sql_id,
       module
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '10' MINUTE
ORDER BY sample_time DESC
FETCH FIRST 100 ROWS ONLY;

需要注意:

  • V$ACTIVE_SESSION_HISTORY 的可见历史受内存和当前负载影响;
  • 历史视图需要相应权限和许可;
  • FETCH FIRST 的使用要符合目标数据库版本;
  • 在 RAC 中通常应考虑 GV$ACTIVE_SESSION_HISTORY,并显示 INST_ID

按等待类别聚合:

SELECT wait_class,
       COUNT(*) AS samples
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '15' MINUTE
GROUP BY wait_class
ORDER BY samples DESC;

这里的 samples 不是等待次数,也不是精确的等待秒数。若采样间隔近似为 1 秒,可以把样本数量作为活动时间的近似,但仍然要明确这是抽样估计。

3. 用 ASH 找到“谁、何时、因为什么”

例如,某接口在 10:00 到 10:05 变慢,可以按 SQL 和等待类别分组:

SELECT sql_id,
       session_state,
       wait_class,
       event,
       COUNT(*) AS samples
FROM   v$active_session_history
WHERE  sample_time >= TO_TIMESTAMP('2025-01-01 10:00:00',
                                   'YYYY-MM-DD HH24:MI:SS')
AND    sample_time <  TO_TIMESTAMP('2025-01-01 10:05:00',
                                   'YYYY-MM-DD HH24:MI:SS')
GROUP BY sql_id, session_state, wait_class, event
ORDER BY samples DESC;

分析步骤是:

  1. 先确定时间窗口,而不是只看“最近”;
  2. 找出活动样本最多的 SQL;
  3. 区分 ON CPUWAITING
  4. 对等待样本按 wait_classevent 聚合;
  5. 回到 SQL 执行计划和业务调用次数;
  6. 检查是否存在阻塞会话、I/O 延迟或并发突增。

如果同一 SQL 的 ASH 样本主要是 ON CPU,应检查逻辑读、连接条件、排序、聚合和执行次数;如果主要是 User I/O,应继续区分物理读、读请求延迟和 SQL 是否不必要地访问了大量数据;如果主要是 ConcurrencyApplication,则应检查锁、闩锁、事务和共享资源。


四、等待事件:等待什么不等于谁有错

1. 等待事件的结构

一个等待事件通常可以理解为:

会话
 ├─ 状态:WAITING 或 ON CPU
 ├─ 事件:等待的资源或同步点
 ├─ 等待类别:User I/O、Commit、Concurrency 等
 ├─ 参数:例如文件号、块号、对象号、锁类型
 └─ 持续时间与等待次数

在当前会话视图中,可以先观察活动会话:

SELECT sid,
       serial#,
       username,
       status,
       sql_id,
       event,
       wait_class,
       state,
       seconds_in_wait,
       blocking_session,
       blocking_session_status
FROM   v$session
WHERE  status = 'ACTIVE'
ORDER BY seconds_in_wait DESC;

常见字段的含义:

  • EVENT:当前等待事件;
  • WAIT_CLASS:等待类别;
  • STATE:当前是等待、刚结束等待,还是其他状态;
  • SECONDS_IN_WAIT:当前等待相关的时间字段;
  • BLOCKING_SESSION:若已识别阻塞者,给出阻塞会话;
  • SQL_ID:当前或相关 SQL 的标识。

V$SESSION 是实时视图,适合回答“现在发生了什么”,不适合单独回答“过去一小时发生了什么”。

2. 常见等待事件的正确解释

db file sequential read

通常表示一次单块数据文件读取。它经常出现在索引访问、按 ROWID 回表或其他单块读取路径中,但事件名称不能证明“索引有问题”。

可能原因包括:

  • 索引选择性差;
  • 大量索引扫描后回表;
  • 缓存命中不足;
  • 随机访问模式;
  • SQL 估算错误导致错误的访问路径。

反例是:某 SQL 使用了索引,但返回了表中大部分行。此时索引并不一定比全表扫描好,单块读等待反而可能增加。

db file scattered read

在传统命名下常与多块读取相关,常见于全表扫描或全索引扫描。但具体行为取决于版本、访问路径和执行环境。不能仅凭事件名称认定“全表扫描就是错误”,因为对大部分数据的顺序读取可能比索引回表更合理。

direct path read

表示绕过部分缓冲区缓存路径的直接读取,可能与大表扫描、临时段读取或其他执行方式有关。需要结合 SQL 计划、对象大小、并行度和 I/O 延迟判断。

log file sync

前台会话提交时等待 LGWR 等日志写入路径完成。常见原因包括:

  • 提交过于频繁;
  • redo 生成量大;
  • 日志写入延迟;
  • 存储或虚拟化层抖动;
  • LGWR 受到 CPU 调度影响。

“出现 log file sync 就应该关闭日志”是错误做法。日志持久性是事务提交语义的重要组成部分,不能为了降低等待而破坏提交可靠性。

enq: TX - row lock contention

通常表示事务正在等待其他事务释放行锁或相关事务资源。诊断时要找到阻塞链:

SELECT sid,
       serial#,
       username,
       event,
       blocking_session,
       sql_id,
       row_wait_obj#
FROM   v$session
WHERE  blocking_session IS NOT NULL;

还应结合事务持续时间、应用请求、未提交事务和锁对象进一步确认。直接杀掉阻塞会话可能导致回滚,回滚时间又可能继续阻塞其他会话。

buffer busy waits

通常说明多个会话对某个缓冲区存在并发访问冲突,但根因可能是热点块、段头、索引叶块、块管理方式或工作负载模式。不能看到该事件就直接增加缓冲区缓存。

cursor: pin S wait on X 和共享游标相关等待

这类等待可能与游标失效、硬解析、共享池竞争、DDL 或对象状态变化有关。需要结合 SQL 版本数量、解析次数、应用是否使用绑定变量以及具体版本行为分析。

3. 等待类别是分类,不是根因

等待类别便于排序和归纳,例如:

  • User I/O:用户 SQL 直接相关的 I/O 等待;
  • System I/O:后台进程或系统级 I/O;
  • Commit:提交路径;
  • Concurrency:并发控制;
  • Application:应用级同步或锁;
  • Configuration:配置导致的等待;
  • Network:网络相关;
  • Scheduler:调度或资源管理相关。

类别只能缩小范围。真正的诊断需要把:

等待事件
+ 等待参数
+ SQL
+ 执行计划
+ 对象
+ 阻塞者
+ 主机资源
+ 时间窗口

联系起来。


五、统计信息:优化器对数据分布的模型

1. 统计信息不是运行时计数器

Oracle 优化器需要估算候选执行计划的成本。它使用的统计信息通常包括:

  • 表的行数和块数;
  • 列的最小值、最大值、非空数量;
  • 列的不同值数量;
  • 直方图;
  • 索引的层级、叶块、聚簇因子等;
  • 分区级或全局统计信息;
  • 相关的系统统计信息。

统计信息描述的是优化器看到的数据模型,不是“这条 SQL 最近运行了多少次”。

这一区分很重要:

  • DBA_TAB_STATISTICS 等视图描述对象统计信息;
  • AWR、ASH、动态性能视图描述运行时活动;
  • 执行计划描述优化器选择的访问路径;
  • 运行时执行统计描述实际行数和资源消耗。

2. 优化器为何会选错计划

设某个谓词选择率为 ss,表中行数为 NN,优化器估算返回行数:

R^=N×s\hat{R} = N \times s

如果真实返回行数为 RR,则估算误差可以用:

E=RR^E = \frac{R}{\hat{R}}

表示。

例如:

  • 表行数 N=10,000,000N = 10,000,000
  • 优化器估算选择率 s=0.001s = 0.001
  • 估算返回行数 R^=10,000\hat{R} = 10,000
  • 实际返回行数 R=2,000,000R = 2,000,000

则:

E=2,000,00010,000=200E = \frac{2,000,000}{10,000}=200

优化器以为只需要处理 1 万行,实际要处理 200 万行。它可能选择索引加回表,而实际全表扫描或其他批量访问路径更合适。

这就是为什么执行计划调优不能只盯着“是否使用索引”。关键问题是:

  1. 基数估算是否接近真实值;
  2. 访问路径是否匹配返回数据量;
  3. 连接顺序和连接方式是否合理;
  4. 排序、聚合、临时空间和并行度是否合适。

3. 直方图解决什么问题

如果列值分布均匀,优化器可用不同值数量近似估算选择率。但现实中可能存在倾斜:

status = 'ACTIVE'   99%
status = 'DELETED'   1%

如果没有足够的分布信息,优化器可能对两个值使用相近的选择率估计。直方图用于表达列值分布,使不同谓词得到不同估算。

但直方图不是越多越好:

  • 它增加统计信息维护复杂度;
  • 数据分布变化后可能过期;
  • 绑定变量和谓词值的变化可能导致不同计划;
  • 重新收集统计信息可能触发计划变化。

可以查看表和列统计信息:

SELECT owner,
       table_name,
       num_rows,
       blocks,
       last_analyzed,
       stale_stats
FROM   dba_tab_statistics
WHERE  owner = 'APP'
AND    table_name = 'ORDERS';

SELECT owner,
       table_name,
       column_name,
       num_distinct,
       num_nulls,
       histogram,
       last_analyzed
FROM   dba_tab_col_statistics
WHERE  owner = 'APP'
AND    table_name = 'ORDERS';

NUM_ROWSBLOCKS 是统计信息中的估计值,不应直接当作实时精确值。

4. 收集统计信息

典型示例:

BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(
      ownname          => 'APP',
      tabname          => 'ORDERS',
      estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
      method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
      cascade          => DBMS_STATS.AUTO_CASCADE,
      no_invalidate    => DBMS_STATS.AUTO_INVALIDATE
  );
END;
/

这些参数的意图是:

  • AUTO_SAMPLE_SIZE:让 Oracle 选择采样策略;
  • SIZE AUTO:根据需要决定直方图;
  • AUTO_CASCADE:由 Oracle 决定是否处理相关索引统计;
  • AUTO_INVALIDATE:由 Oracle 决定游标失效策略。

但“自动”不等于不会产生风险。收集统计信息可能导致:

  • 优化器重新选择计划;
  • 硬解析或游标重新验证;
  • 统计信息写入占用 CPU、I/O 和临时空间;
  • 大表收集时间较长;
  • 业务高峰期间资源竞争。

因此应在业务低峰执行,并在执行前后保存:

  • SQL ID;
  • 执行计划;
  • SQL 资源指标;
  • 统计信息时间;
  • 业务响应时间。

如果需要恢复,Oracle 提供统计信息历史能力和相应的恢复接口,但前提是历史尚未过期且权限、保留策略和对象范围都满足要求。恢复统计信息之前,应确认问题确实由统计信息变化引起,而不是把其他变化覆盖掉。

5. 动态采样、SQL Plan Management 和 Hint 的边界

当持久化统计不足时,优化器可能使用动态采样或其他自适应机制改善估算。它不是统计信息维护的替代品:

  • 动态采样有额外优化阶段开销;
  • 复杂数据分布仍可能估算错误;
  • 运行时数据变化可能使一次采样不具代表性。

Hint 也不是永久修复手段。Hint 直接影响某次 SQL 的优化决策,但可能因:

  • 表结构变化;
  • 数据分布变化;
  • 版本变化;
  • Hint 无效或被忽略;
  • SQL 文本或别名变化;

而失去预期效果。

生产调优应优先修正 SQL 语义、数据访问方式和统计信息;需要稳定计划时,再评估 SQL Plan Management、SQL Profile、基线或有限范围的 Hint。不同功能涉及不同许可和版本能力,不能混为一谈。


六、从统计信息回到实际执行计划

1. EXPLAIN PLAN 不等于实际执行结果

以下命令展示的是优化器为某次解析生成的计划:

EXPLAIN PLAN FOR
SELECT o.order_id
FROM   app.orders o
WHERE  o.customer_id = 1001;

SELECT *
FROM   TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +PREDICATE'));

它有两个限制:

  1. 它可能不是应用实际执行的游标;
  2. 它没有真实执行行数和真实资源消耗。

更可靠的方式是查看已经执行的游标:

SELECT *
FROM   TABLE(
  DBMS_XPLAN.DISPLAY_CURSOR(
      sql_id          => '9abc123def456',
      format          => 'ALLSTATS LAST +PEEKED_BINDS +PREDICATE'
  )
);

要看到实际行数,执行时通常需要采集执行统计,例如:

SELECT /*+ GATHER_PLAN_STATISTICS */
       o.order_id
FROM   app.orders o
WHERE  o.customer_id = 1001;

也可以使用适当的会话或系统级统计配置,但范围越大,额外开销越高。

2. 重点看估算行数和实际行数

执行计划中常见两组重要列:

  • E-Rows:优化器估算行数;
  • A-Rows:实际执行行数。

假设某连接节点显示:

Operation              E-Rows        A-Rows
HASH JOIN              1000          2000000
TABLE ACCESS           1000          2000000

这说明估算误差约为 2000 倍。此时应优先检查:

  • 谓词列统计信息;
  • 数据倾斜和直方图;
  • 多列相关性;
  • 隐式类型转换;
  • 绑定变量行为;
  • 分区裁剪是否发生;
  • SQL 条件是否与业务实际选择性一致。

如果 E-RowsA-Rows 接近,但 SQL 仍慢,问题可能在:

  • 单次访问量本身太大;
  • I/O 延迟;
  • CPU 资源不足;
  • 并发争用;
  • 排序或临时空间;
  • 执行次数过多。

3. 估算正确也可能计划不适合

反例:

  • 估算返回 100 万行;
  • 实际也返回 100 万行;
  • 但 SQL 只需要 10 个字段中的 2 个字段;
  • 当前计划通过低选择性索引逐行回表。

这时统计信息可能是正确的,问题是访问路径的成本模型和物理设计不适合工作负载。可以评估:

  • 更合理的索引前导列;
  • 覆盖索引是否值得;
  • 全表扫描或分区扫描;
  • SQL 是否可以减少返回列和行;
  • 是否存在不必要的排序、去重或函数计算。

因此“更新统计信息”不能成为所有计划问题的统一答案。


七、从 AWR、ASH 和执行计划建立因果链

一个可靠的调优结论应至少形成如下链路:

业务症状
  ↓
时间窗口和影响范围
  ↓
AWR:区间负载、DB time、CPU、等待类别
  ↓
ASH:具体 SQL、会话、阻塞者和时间分布
  ↓
执行计划:访问路径、连接顺序、E-Rows/A-Rows
  ↓
统计信息、对象结构和主机资源
  ↓
修复措施与回归验证

示例:接口从 200 ms 变为 5 秒

第一步:确认数据库是否真的变慢

查看该时间窗口的:

  • 数据库时间;
  • 前台等待时间;
  • 事务提交时间;
  • CPU;
  • 用户 I/O;
  • 活跃会话数量;
  • SQL 执行次数。

如果应用响应时间增加,但数据库时间没有增加,应优先检查连接池、网络或应用线程池,而不是直接改 SQL。

第二步:用 ASH 定位活动来源

如果 ASH 显示某 SQL 的样本突然增多,并且主要等待 User I/O,可以进一步查询:

  • 该 SQL 的执行次数是否突增;
  • 单次逻辑读和物理读是否增加;
  • 是否出现新的子游标;
  • 是否发生计划变化;
  • 对象是否刚刚装载大量数据。

第三步:比较实际计划

使用 DBMS_XPLAN.DISPLAY_CURSOR 比较正常时和异常时的:

  • 计划哈希值;
  • E-RowsA-Rows
  • 连接顺序;
  • 索引、全表扫描和分区裁剪;
  • 临时空间;
  • 并行执行;
  • 绑定变量窥视结果。

第四步:判断根因

可能得到不同结论:

  • 统计信息过期,导致基数估算错误;
  • 数据倾斜变化,原有直方图不再代表现实;
  • SQL 文本未变但子游标因环境或绑定变量产生不同计划;
  • 业务调用次数增加十倍,单次执行没有变慢;
  • 存储延迟增加,SQL 计划没有变化;
  • 事务未提交,后续请求在等待行锁。

每一种结论的修复方式都不同。


八、容量分析:从“现在很慢”推导“还能承受多少”

1. 容量不是单一指标

数据库容量至少包含:

  • CPU 容量;
  • I/O 吞吐和 I/O 延迟容量;
  • 内存容量;
  • 并发会话容量;
  • 日志写入容量;
  • 锁和事务并发容量;
  • 临时空间容量;
  • 表空间和归档空间容量;
  • 连接池及网络容量。

“CPU 还有 30%”不能证明系统还有 30% 的整体容量,因为 I/O、日志、锁或连接数可能已经先达到瓶颈。

2. Little 定律与活动会话

排队系统中常用 Little 定律:

L=λRL = \lambda R

其中:

  • LL:系统中的平均请求数;
  • λ\lambda:吞吐率;
  • RR:平均响应时间。

在数据库分析中,可以近似理解为:

AASTPS×每事务平均数据库时间AAS \approx TPS \times \text{每事务平均数据库时间}

例如:

  • 每秒 200 个事务;
  • 每个事务平均消耗 20 ms 数据库时间。

则:

AAS=200×0.02=4AAS = 200 \times 0.02 = 4

这表示平均有约 4 个活动会话。

如果吞吐量不变,而单事务数据库时间从 20 ms 增加到 100 ms:

AAS=200×0.1=20AAS = 200 \times 0.1 = 20

活动会话增加五倍,连接池、锁竞争和 CPU 争用可能随之恶化。性能问题常常不是线性增长:接近资源上限后,排队时间会快速增加。

3. 用服务需求估算资源上限

设某类请求每秒到达 λ\lambda 次,每次在某资源上平均消耗 DD 秒,则该资源所需利用率近似为:

U=λDU = \lambda D

例如,某 SQL 每秒执行 100 次,每次平均消耗 8 ms CPU:

UCPU=100×0.008=0.8U_{\text{CPU}} = 100 \times 0.008 = 0.8

这相当于消耗 0.8 个 CPU 核。如果数据库可用 CPU 配额只有 1 个核,CPU 已接近饱和;如果可用 8 个核,CPU 可能不是主要瓶颈。

对 I/O 也可以使用类似思路。若每秒产生 500 次 I/O 请求,每次存储服务时间平均 4 ms:

UI/O=500×0.004=2U_{\text{I/O}} = 500 \times 0.004 = 2

这意味着单一串行服务能力不足,实际系统可能依赖多个并行服务队列;不能简单把这个结果当作设备利用率,但它说明“请求率乘以单次服务时间”已经超过单服务通道能力。

4. 不能用平均值掩盖峰值

容量模型应至少分别计算:

  • 工作日平均;
  • 业务高峰;
  • 批处理窗口;
  • 发布或数据装载窗口;
  • 故障降级场景;
  • 副本、备份和统计信息收集期间。

例如某小时平均 TPS 为 100,但五分钟峰值 TPS 为 500。用 100 TPS 规划 CPU 和 I/O,可能在高峰时直接进入排队区。

AWR 适合观察区间总量和趋势,ASH 适合定位尖峰中的活动会话,操作系统监控则用于验证主机级 CPU、内存、I/O 和网络。


九、表空间、临时空间和日志容量

1. 表空间容量

查看永久表空间使用情况时,应明确使用的是数据文件总大小、已使用空间还是可自动扩展上限。不同查询反映不同问题。

例如可以先查看数据文件:

SELECT tablespace_name,
       file_name,
       bytes / 1024 / 1024 AS size_mb,
       autoextensible,
       maxbytes / 1024 / 1024 AS max_mb
FROM   dba_data_files
ORDER BY tablespace_name, file_name;

这里的 MAXBYTES 只是数据文件的自动扩展上限,不代表底层文件系统或 ASM 磁盘组真的有足够空间。

因此容量判断至少要同时验证:

  • 表空间剩余可用空间;
  • 数据文件是否允许自动扩展;
  • 自动扩展上限;
  • 文件系统或 ASM 磁盘组剩余空间;
  • 增长速度;
  • 大对象、分区和索引的增长来源。

“表空间还有 20%”也不一定安全。如果每天增长 50 GB,而底层只剩 10 GB,系统仍会很快失败。

2. 临时表空间

排序、哈希连接、临时结果集和某些并行操作可能使用临时表空间。检查临时文件:

SELECT tablespace_name,
       file_name,
       bytes / 1024 / 1024 AS size_mb,
       autoextensible,
       maxbytes / 1024 / 1024 AS max_mb
FROM   dba_temp_files
ORDER BY tablespace_name, file_name;

临时空间增长的诊断路径包括:

  1. 观察临时空间当前使用量;
  2. 找到消耗临时空间的会话和 SQL;
  3. 查看执行计划中的排序、哈希和并行操作;
  4. 判断是数据量增长、内存不足、计划变化还是 SQL 逻辑问题;
  5. 扩容前确认底层空间和增长上限。

单纯扩大临时表空间只能延后失败,不能修复错误连接顺序或不必要排序。

3. 归档和日志容量

日志相关容量包括:

  • redo 日志组;
  • 归档日志空间;
  • 备份保留;
  • Data Guard 传输或应用延迟;
  • 恢复窗口要求。

如果归档目的地空间耗尽,数据库可能出现严重影响甚至无法继续正常归档。容量监控必须观察增长速率和故障恢复路径,而不是只设置一个固定百分比阈值。


十、锁、事务和等待链

等待事件经常只是阻塞链的表象。一个完整的锁问题通常是:

会话 A 开启事务并修改行
        │
        │ 未提交
        ▼
会话 B 修改同一行
        │
        ▼
enq: TX - row lock contention
        │
        ▼
会话 C 可能继续等待 B 或其他资源

诊断时应同时观察:

  • 阻塞会话;
  • 被阻塞会话;
  • 事务开始时间;
  • 当前 SQL;
  • 最后一次提交时间;
  • 应用请求和连接归属;
  • 回滚段和事务持续时间。

不能把所有锁等待归因于“数据库锁配置不合理”。很多锁问题来自应用事务边界:

  • 开启事务后执行远程调用;
  • 修改数据后等待用户操作;
  • 连接归还池前未提交或回滚;
  • 批量操作过大;
  • 异常路径没有正确结束事务。

修复阻塞的优先级通常是先停止或修正制造长事务的业务路径,再评估是否需要终止会话。终止会话不是无成本操作,可能触发大量回滚,并使恢复时间不可预测。


十一、常见误解与失败诊断

误解一:Top Wait Event 第一名就是根因

某等待事件累计时间最多,可能只是因为执行次数特别多。应同时看:

平均等待时间=总等待时间等待次数\text{平均等待时间} = \frac{\text{总等待时间}}{\text{等待次数}}

例如:

  • 事件 A:等待 100 万次,总计 1000 秒,平均 1 ms;
  • 事件 B:等待 1000 次,总计 500 秒,平均 500 ms。

事件 A 的累计时间更高,但事件 B 可能更直接反映存储或锁的异常。

误解二:出现物理读就应该增加内存

物理读可能由以下原因造成:

  • SQL 扫描了过多数据;
  • 索引回表次数过多;
  • 工作集本来就超过内存;
  • 缓存被大量无效访问冲刷;
  • 存储延迟异常。

增加缓存只能缓解部分问题。如果 SQL 每次都扫描一个远大于缓存的表,缓存增加不一定有效。

误解三:执行计划用了索引就一定更快

索引访问适合高选择性、访问列和数据布局匹配的场景。返回大量行时,索引逐行回表可能比顺序扫描更慢。

判断依据应是:

  • 返回行数;
  • E-RowsA-Rows
  • 逻辑读和物理读;
  • 单次执行时间;
  • 执行次数;
  • 并发下的总资源消耗。

误解四:重新收集统计信息一定能修复慢 SQL

如果真正根因是:

  • 锁等待;
  • 存储延迟;
  • 提交过于频繁;
  • SQL 执行次数暴增;
  • 应用连接池排队;
  • 主机 CPU 配额不足;

重新收集统计信息不仅无效,还可能改变原本稳定的执行计划。

误解五:ASH 没有记录就说明没有问题

ASH 是采样系统。短 SQL、瞬时锁和采样间隔之间的事件可能漏掉。对短时尖峰,应结合:

  • 应用日志;
  • 实时 V$ 视图;
  • SQL 监控能力;
  • 操作系统监控;
  • 业务时间戳;
  • 数据库审计或专门追踪机制。

不能把 ASH 当作完整事件日志。

误解六:EXPLAIN PLAN 就是应用实际运行的计划

实际游标可能因以下原因不同:

  • 绑定变量不同;
  • 子游标不同;
  • 会话环境不同;
  • 统计信息变化;
  • SQL Plan Management;
  • 自适应游标行为;
  • 对象或分区状态变化。

调优时应优先检查实际游标和实际执行统计。


十二、一个可重复的生产诊断流程

第一步:固定事实边界

记录:

  • 数据库版本和补丁;
  • 单实例还是 RAC;
  • 数据库时间、主机时间和应用时间是否一致;
  • 问题开始和结束时间;
  • 受影响的接口、用户和 SQL;
  • 是否发生发布、统计信息收集、批处理、备份或数据装载。

时间边界错误会导致 AWR、ASH 和应用日志互相对不上。

第二步:判断是数据库整体问题还是局部 SQL 问题

先看:

  • DB time 是否上升;
  • AAS 是否上升;
  • DB CPU 是否上升;
  • 等待类别是否改变;
  • 并发请求是否改变。

若只有一个 SQL 变慢,重点转向执行计划、统计信息和对象访问。若大量 SQL 同时变慢,重点检查 CPU、I/O、日志、锁、网络、存储和资源管理。

第三步:用 AWR 看区间差异

比较正常区间与异常区间:

  • 业务吞吐是否变化;
  • 每秒逻辑读、物理读、redo 和事务数;
  • DB time 每秒;
  • DB CPU 每秒;
  • 主要等待事件;
  • SQL 排名变化;
  • 主机资源变化。

必须看“每秒”或“每次执行”的归一化指标,不能只看总量。

第四步:用 ASH 定位并发来源

观察:

  • 哪些 SQL 占用活动样本最多;
  • 哪些模块或服务产生活动;
  • 是 CPU 还是等待;
  • 是否形成阻塞链;
  • 问题是否集中在某个实例;
  • 是否存在计划切换。

第五步:检查执行计划和统计信息

对候选 SQL:

  1. 找到实际 SQL_ID
  2. 查看子游标;
  3. 查看实际执行计划;
  4. 对比 E-RowsA-Rows
  5. 查看谓词是否发生隐式转换;
  6. 检查表、列、索引统计信息;
  7. 核对分区裁剪和连接条件;
  8. 评估修复后的回归风险。

第六步:验证修复而不是只验证命令成功

修复后的验证应包括:

  • 同样时间窗口的 AAS;
  • 单次执行 DB time;
  • CPU、逻辑读、物理读;
  • 等待事件和平均等待时间;
  • 执行计划是否符合预期;
  • 业务响应时间;
  • 并发高峰表现;
  • 回滚或恢复方案是否可用。

“统计信息收集成功”“索引创建成功”只说明操作完成,不说明性能问题已经解决。


十三、监控查询示例:把指标放到同一张图上

下面的查询用于查看某段时间内 ASH 按 SQL 聚合的活动样本:

SELECT sql_id,
       COUNT(*) AS active_samples,
       SUM(CASE WHEN session_state = 'ON CPU' THEN 1 ELSE 0 END)
         AS on_cpu_samples,
       SUM(CASE WHEN session_state = 'WAITING' THEN 1 ELSE 0 END)
         AS waiting_samples
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '30' MINUTE
GROUP BY sql_id
ORDER BY active_samples DESC;

解释:

  • active_samples:该 SQL 出现在活动会话采样中的次数;
  • on_cpu_samples:近似反映 CPU 活动;
  • waiting_samples:近似反映等待活动;
  • 这些值不是精确执行次数,也不是精确等待调用次数。

若要查看当前系统级等待:

SELECT event,
       wait_class,
       total_waits,
       time_waited_micro / 1000000 AS time_waited_seconds
FROM   v$system_event
WHERE  wait_class <> 'Idle'
ORDER BY time_waited_micro DESC;

系统累计视图必须结合启动时间或快照差分使用。数据库运行了很久时,累计总量很大并不等于最近发生了异常。

查看数据库启动时间:

SELECT startup_time
FROM   v$instance;

如果没有 AWR,也可以在两个时间点采集动态性能视图并自行计算增量,但这不等同于 AWR 的完整历史能力,且采集过程本身需要设计好权限、频率和存储。


十四、监控系统应如何保存指标

生产监控不应只保存一个“数据库 CPU 百分比”。至少需要保存以下维度:

数据库层

  • DB time;
  • DB CPU;
  • AAS;
  • 每秒执行次数;
  • 每次执行逻辑读、物理读、CPU 时间;
  • 提交和回滚次数;
  • redo 生成速率;
  • 等待类别和主要等待事件;
  • 阻塞会话数量;
  • 长事务;
  • 临时空间使用;
  • 表空间和归档空间增长。

SQL 层

  • SQL_ID
  • 计划哈希值;
  • 执行次数;
  • 平均和分位响应时间;
  • CPU 时间;
  • DB time;
  • 逻辑读和物理读;
  • 返回行数;
  • 子游标数量;
  • 计划切换时间。

主机和存储层

  • CPU 使用率和运行队列;
  • 可用内存和交换;
  • IOPS;
  • 吞吐;
  • I/O 延迟;
  • 文件系统或 ASM 容量;
  • 网络延迟和丢包;
  • 虚拟机或容器 CPU、I/O 配额。

指标必须带有时间窗口、实例、服务、模块和环境标签。否则同一个 SQL 在不同实例、不同服务和不同业务路径下的行为会被混在一起。


十五、生产取舍:观测能力本身也有成本

AWR、ASH、执行统计和追踪会消耗:

  • CPU;
  • 内存;
  • I/O;
  • 数据字典空间;
  • 日志和报告存储;
  • 运维人员的分析时间。

因此应区分三种场景:

日常监控

使用低开销的系统指标、应用指标、连接和事务指标,发现趋势和 SLO 违约。

问题定位

在确定时间窗口内使用 AWR、ASH、实时视图和实际执行计划,减少全库范围的长期高成本采集。

深度实验

在可回滚、可隔离的环境中启用更详细的执行统计或 SQL 追踪,验证假设后再推广到生产。

调优不是把所有诊断开关永久打开,而是在足够观测和可接受开销之间取得平衡。


Oracle 性能分析的核心不是记住某个等待事件对应某个参数,而是建立可验证的因果关系:

业务吞吐数据库时间CPU 与等待具体 SQL 和会话执行计划与统计信息资源容量和并发边界\text{业务吞吐} \rightarrow \text{数据库时间} \rightarrow \text{CPU 与等待} \rightarrow \text{具体 SQL 和会话} \rightarrow \text{执行计划与统计信息} \rightarrow \text{资源容量和并发边界}

AWR 提供区间视角,ASH 提供活动会话视角,等待事件提供资源等待视角,统计信息和执行计划提供优化器视角,容量模型则把这些结果转化为对峰值、增长和故障余量的判断。只有把这些视角放在同一条时间线上,调优结论才不会停留在“看到一个事件就修改一个参数”。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论

0 条讨论
0/1000
还没有评论,来聊聊你的看法
视图或在正确的实例上生成报告。\n\n### 2. 快照间隔和保留时间不是性能结论\n\nOracle 环境中的快照间隔和保留时间可以配置。某些安装的常见默认值是每小时生成一次并保留若干天,但具体值取决于版本、安装方式和管理员配置,不能把默认值当作规范保证。\n\n可以通过以下查询查看配置:\n\n```sql\nSELECT snap_interval,\n retention,\n topnsql\nFROM dba_hist_wr_control;\n```\n\n这些字段分别描述快照间隔、保留策略和 SQL 记录范围。查询通常需要相应的数据字典权限。\n\n在生产环境中,修改 AWR 控制参数会影响存储量和诊断能力,不应为了“让报告更详细”而无限缩短间隔或无限延长保留时间。更高的采样或保留成本需要结合问题发生频率、存储空间和合规要求评估。\n\n### 3. AWR 报告的本质是“两个状态的差分”\n\n例如,某 SQL 在快照 100 时累计:\n\n- 执行次数:1000;\n- DB time:500 秒;\n- 逻辑读:1,000,000。\n\n在快照 101 时累计:\n\n- 执行次数:1100;\n- DB time:900 秒;\n- 逻辑读:1,800,000。\n\n那么区间增量为:\n\n- 执行次数:100;\n- DB time:400 秒;\n- 逻辑读:800,000。\n\n区间平均值为:\n\n\\[\n\\text{平均每次执行 DB time}\n=\n\\frac{400}{100}\n=\n4\\text{ 秒}\n\\]\n\n\\[\n\\text{平均每次执行逻辑读}\n=\n\\frac{800000}{100}\n=\n8000\\text{ 次}\n\\]\n\n不能直接用累计值判断当前区间性能,否则会把数据库启动以来的历史工作负载混入结论。\n\n### 4. 生成 AWR 报告\n\nOracle 常见环境中可以使用 SQL*Plus 提供的报告脚本:\n\n```sql\n@?/rdbms/admin/awrrpt.sql\n```\n\n脚本通常会依次询问:\n\n1. 报告类型,例如单实例或 RAC;\n2. 报告格式;\n3. 起始快照;\n4. 结束快照;\n5. 输出文件名。\n\n它要求执行者具有足够的访问权限,并且环境必须具备相应的 AWR 能力。\n\n报告结果应按以下顺序阅读:\n\n1. 报告时间范围和数据库实例;\n2. Load Profile;\n3. Top Timed Events;\n4. Time Model Statistics;\n5. SQL 统计;\n6. Instance Activity;\n7. 主机 CPU 和 I/O 信息;\n8. 与业务峰值、发布、批处理时间对照。\n\n不要看到 `Top Timed Events` 的第一项就立刻修改参数。它只说明某个事件在该时间区间累计消耗较多时间,还需要结合执行次数、并发量、调用链和资源上限。\n\n### 5. AWR 的许可证边界\n\nAWR、ASH 以及相关历史诊断能力属于 Oracle Diagnostics Pack 的范围。是否可以在某个环境中使用,取决于 Oracle 版本、版本选项、部署方式和许可证合同。不能因为视图存在就默认可以在任何环境中自由使用。\n\n在生产环境使用 AWR 报告、历史 ASH 或相关接口前,应由负责 Oracle 许可证的团队确认授权范围。本文讨论的是功能语义,不替代许可证判断。\n\n---\n\n## 三、ASH:回答“某一时刻哪些会话在做什么”\n\n### 1. ASH 的观察单位是活动会话\n\nASH,即 Active Session History,面向“活动会话”采样。一个样本通常包含类似信息:\n\n- 样本时间;\n- 会话标识;\n- SQL 标识;\n- 会话当前是否在 CPU 上;\n- 等待事件和等待参数;\n- 等待类别;\n- 当前对象或数据文件等上下文;\n- 阻塞会话相关信息;\n- 服务名、模块、动作等会话属性。\n\nASH 的重要特点是:\n\n- 它不是每次等待开始和结束都完整记录;\n- 它主要记录活动会话;\n- 它通常以约一秒级的采样粒度观察活动;\n- 短于采样间隔的瞬时事件可能被漏采;\n- 长时间持续的等待更容易被观察到。\n\n因此,ASH 是抽样历史,不是完整审计日志。某条 SQL 没出现在 ASH 中,不一定代表它从未执行过,可能是执行太快、未处于活动状态,或者历史数据已被覆盖。\n\n### 2. 当前 ASH 与历史 ASH\n\n当前内存中的活动会话历史可通过 `V$ACTIVE_SESSION_HISTORY` 查询。历史持久化数据通常通过 `DBA_HIST_ACTIVE_SESS_HISTORY` 查询。\n\n一个查看最近活动会话的示例:\n\n```sql\nSELECT sample_time,\n session_id,\n session_serial#,\n session_state,\n event,\n wait_class,\n sql_id,\n module\nFROM v$active_session_history\nWHERE sample_time >= SYSTIMESTAMP - INTERVAL '10' MINUTE\nORDER BY sample_time DESC\nFETCH FIRST 100 ROWS ONLY;\n```\n\n需要注意:\n\n- `V$ACTIVE_SESSION_HISTORY` 的可见历史受内存和当前负载影响;\n- 历史视图需要相应权限和许可;\n- `FETCH FIRST` 的使用要符合目标数据库版本;\n- 在 RAC 中通常应考虑 `GV$ACTIVE_SESSION_HISTORY`,并显示 `INST_ID`。\n\n按等待类别聚合:\n\n```sql\nSELECT wait_class,\n COUNT(*) AS samples\nFROM v$active_session_history\nWHERE sample_time >= SYSTIMESTAMP - INTERVAL '15' MINUTE\nGROUP BY wait_class\nORDER BY samples DESC;\n```\n\n这里的 `samples` 不是等待次数,也不是精确的等待秒数。若采样间隔近似为 1 秒,可以把样本数量作为活动时间的近似,但仍然要明确这是抽样估计。\n\n### 3. 用 ASH 找到“谁、何时、因为什么”\n\n例如,某接口在 10:00 到 10:05 变慢,可以按 SQL 和等待类别分组:\n\n```sql\nSELECT sql_id,\n session_state,\n wait_class,\n event,\n COUNT(*) AS samples\nFROM v$active_session_history\nWHERE sample_time >= TO_TIMESTAMP('2025-01-01 10:00:00',\n 'YYYY-MM-DD HH24:MI:SS')\nAND sample_time \u003c TO_TIMESTAMP('2025-01-01 10:05:00',\n 'YYYY-MM-DD HH24:MI:SS')\nGROUP BY sql_id, session_state, wait_class, event\nORDER BY samples DESC;\n```\n\n分析步骤是:\n\n1. 先确定时间窗口,而不是只看“最近”;\n2. 找出活动样本最多的 SQL;\n3. 区分 `ON CPU` 和 `WAITING`;\n4. 对等待样本按 `wait_class` 和 `event` 聚合;\n5. 回到 SQL 执行计划和业务调用次数;\n6. 检查是否存在阻塞会话、I/O 延迟或并发突增。\n\n如果同一 SQL 的 ASH 样本主要是 `ON CPU`,应检查逻辑读、连接条件、排序、聚合和执行次数;如果主要是 `User I/O`,应继续区分物理读、读请求延迟和 SQL 是否不必要地访问了大量数据;如果主要是 `Concurrency` 或 `Application`,则应检查锁、闩锁、事务和共享资源。\n\n---\n\n## 四、等待事件:等待什么不等于谁有错\n\n### 1. 等待事件的结构\n\n一个等待事件通常可以理解为:\n\n```text\n会话\n ├─ 状态:WAITING 或 ON CPU\n ├─ 事件:等待的资源或同步点\n ├─ 等待类别:User I/O、Commit、Concurrency 等\n ├─ 参数:例如文件号、块号、对象号、锁类型\n └─ 持续时间与等待次数\n```\n\n在当前会话视图中,可以先观察活动会话:\n\n```sql\nSELECT sid,\n serial#,\n username,\n status,\n sql_id,\n event,\n wait_class,\n state,\n seconds_in_wait,\n blocking_session,\n blocking_session_status\nFROM v$session\nWHERE status = 'ACTIVE'\nORDER BY seconds_in_wait DESC;\n```\n\n常见字段的含义:\n\n- `EVENT`:当前等待事件;\n- `WAIT_CLASS`:等待类别;\n- `STATE`:当前是等待、刚结束等待,还是其他状态;\n- `SECONDS_IN_WAIT`:当前等待相关的时间字段;\n- `BLOCKING_SESSION`:若已识别阻塞者,给出阻塞会话;\n- `SQL_ID`:当前或相关 SQL 的标识。\n\n`V$SESSION` 是实时视图,适合回答“现在发生了什么”,不适合单独回答“过去一小时发生了什么”。\n\n### 2. 常见等待事件的正确解释\n\n#### `db file sequential read`\n\n通常表示一次单块数据文件读取。它经常出现在索引访问、按 ROWID 回表或其他单块读取路径中,但事件名称不能证明“索引有问题”。\n\n可能原因包括:\n\n- 索引选择性差;\n- 大量索引扫描后回表;\n- 缓存命中不足;\n- 随机访问模式;\n- SQL 估算错误导致错误的访问路径。\n\n反例是:某 SQL 使用了索引,但返回了表中大部分行。此时索引并不一定比全表扫描好,单块读等待反而可能增加。\n\n#### `db file scattered read`\n\n在传统命名下常与多块读取相关,常见于全表扫描或全索引扫描。但具体行为取决于版本、访问路径和执行环境。不能仅凭事件名称认定“全表扫描就是错误”,因为对大部分数据的顺序读取可能比索引回表更合理。\n\n#### `direct path read`\n\n表示绕过部分缓冲区缓存路径的直接读取,可能与大表扫描、临时段读取或其他执行方式有关。需要结合 SQL 计划、对象大小、并行度和 I/O 延迟判断。\n\n#### `log file sync`\n\n前台会话提交时等待 LGWR 等日志写入路径完成。常见原因包括:\n\n- 提交过于频繁;\n- redo 生成量大;\n- 日志写入延迟;\n- 存储或虚拟化层抖动;\n- LGWR 受到 CPU 调度影响。\n\n“出现 `log file sync` 就应该关闭日志”是错误做法。日志持久性是事务提交语义的重要组成部分,不能为了降低等待而破坏提交可靠性。\n\n#### `enq: TX - row lock contention`\n\n通常表示事务正在等待其他事务释放行锁或相关事务资源。诊断时要找到阻塞链:\n\n```sql\nSELECT sid,\n serial#,\n username,\n event,\n blocking_session,\n sql_id,\n row_wait_obj#\nFROM v$session\nWHERE blocking_session IS NOT NULL;\n```\n\n还应结合事务持续时间、应用请求、未提交事务和锁对象进一步确认。直接杀掉阻塞会话可能导致回滚,回滚时间又可能继续阻塞其他会话。\n\n#### `buffer busy waits`\n\n通常说明多个会话对某个缓冲区存在并发访问冲突,但根因可能是热点块、段头、索引叶块、块管理方式或工作负载模式。不能看到该事件就直接增加缓冲区缓存。\n\n#### `cursor: pin S wait on X` 和共享游标相关等待\n\n这类等待可能与游标失效、硬解析、共享池竞争、DDL 或对象状态变化有关。需要结合 SQL 版本数量、解析次数、应用是否使用绑定变量以及具体版本行为分析。\n\n### 3. 等待类别是分类,不是根因\n\n等待类别便于排序和归纳,例如:\n\n- `User I/O`:用户 SQL 直接相关的 I/O 等待;\n- `System I/O`:后台进程或系统级 I/O;\n- `Commit`:提交路径;\n- `Concurrency`:并发控制;\n- `Application`:应用级同步或锁;\n- `Configuration`:配置导致的等待;\n- `Network`:网络相关;\n- `Scheduler`:调度或资源管理相关。\n\n类别只能缩小范围。真正的诊断需要把:\n\n```text\n等待事件\n+ 等待参数\n+ SQL\n+ 执行计划\n+ 对象\n+ 阻塞者\n+ 主机资源\n+ 时间窗口\n```\n\n联系起来。\n\n---\n\n## 五、统计信息:优化器对数据分布的模型\n\n### 1. 统计信息不是运行时计数器\n\nOracle 优化器需要估算候选执行计划的成本。它使用的统计信息通常包括:\n\n- 表的行数和块数;\n- 列的最小值、最大值、非空数量;\n- 列的不同值数量;\n- 直方图;\n- 索引的层级、叶块、聚簇因子等;\n- 分区级或全局统计信息;\n- 相关的系统统计信息。\n\n统计信息描述的是优化器看到的数据模型,不是“这条 SQL 最近运行了多少次”。\n\n这一区分很重要:\n\n- `DBA_TAB_STATISTICS` 等视图描述对象统计信息;\n- AWR、ASH、动态性能视图描述运行时活动;\n- 执行计划描述优化器选择的访问路径;\n- 运行时执行统计描述实际行数和资源消耗。\n\n### 2. 优化器为何会选错计划\n\n设某个谓词选择率为 \\(s\\),表中行数为 \\(N\\),优化器估算返回行数:\n\n\\[\n\\hat{R} = N \\times s\n\\]\n\n如果真实返回行数为 \\(R\\),则估算误差可以用:\n\n\\[\nE = \\frac{R}{\\hat{R}}\n\\]\n\n表示。\n\n例如:\n\n- 表行数 \\(N = 10,000,000\\);\n- 优化器估算选择率 \\(s = 0.001\\);\n- 估算返回行数 \\(\\hat{R} = 10,000\\);\n- 实际返回行数 \\(R = 2,000,000\\)。\n\n则:\n\n\\[\nE = \\frac{2,000,000}{10,000}=200\n\\]\n\n优化器以为只需要处理 1 万行,实际要处理 200 万行。它可能选择索引加回表,而实际全表扫描或其他批量访问路径更合适。\n\n这就是为什么执行计划调优不能只盯着“是否使用索引”。关键问题是:\n\n1. 基数估算是否接近真实值;\n2. 访问路径是否匹配返回数据量;\n3. 连接顺序和连接方式是否合理;\n4. 排序、聚合、临时空间和并行度是否合适。\n\n### 3. 直方图解决什么问题\n\n如果列值分布均匀,优化器可用不同值数量近似估算选择率。但现实中可能存在倾斜:\n\n```text\nstatus = 'ACTIVE' 99%\nstatus = 'DELETED' 1%\n```\n\n如果没有足够的分布信息,优化器可能对两个值使用相近的选择率估计。直方图用于表达列值分布,使不同谓词得到不同估算。\n\n但直方图不是越多越好:\n\n- 它增加统计信息维护复杂度;\n- 数据分布变化后可能过期;\n- 绑定变量和谓词值的变化可能导致不同计划;\n- 重新收集统计信息可能触发计划变化。\n\n可以查看表和列统计信息:\n\n```sql\nSELECT owner,\n table_name,\n num_rows,\n blocks,\n last_analyzed,\n stale_stats\nFROM dba_tab_statistics\nWHERE owner = 'APP'\nAND table_name = 'ORDERS';\n\nSELECT owner,\n table_name,\n column_name,\n num_distinct,\n num_nulls,\n histogram,\n last_analyzed\nFROM dba_tab_col_statistics\nWHERE owner = 'APP'\nAND table_name = 'ORDERS';\n```\n\n`NUM_ROWS` 和 `BLOCKS` 是统计信息中的估计值,不应直接当作实时精确值。\n\n### 4. 收集统计信息\n\n典型示例:\n\n```sql\nBEGIN\n DBMS_STATS.GATHER_TABLE_STATS(\n ownname => 'APP',\n tabname => 'ORDERS',\n estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,\n method_opt => 'FOR ALL COLUMNS SIZE AUTO',\n cascade => DBMS_STATS.AUTO_CASCADE,\n no_invalidate => DBMS_STATS.AUTO_INVALIDATE\n );\nEND;\n/\n```\n\n这些参数的意图是:\n\n- `AUTO_SAMPLE_SIZE`:让 Oracle 选择采样策略;\n- `SIZE AUTO`:根据需要决定直方图;\n- `AUTO_CASCADE`:由 Oracle 决定是否处理相关索引统计;\n- `AUTO_INVALIDATE`:由 Oracle 决定游标失效策略。\n\n但“自动”不等于不会产生风险。收集统计信息可能导致:\n\n- 优化器重新选择计划;\n- 硬解析或游标重新验证;\n- 统计信息写入占用 CPU、I/O 和临时空间;\n- 大表收集时间较长;\n- 业务高峰期间资源竞争。\n\n因此应在业务低峰执行,并在执行前后保存:\n\n- SQL ID;\n- 执行计划;\n- SQL 资源指标;\n- 统计信息时间;\n- 业务响应时间。\n\n如果需要恢复,Oracle 提供统计信息历史能力和相应的恢复接口,但前提是历史尚未过期且权限、保留策略和对象范围都满足要求。恢复统计信息之前,应确认问题确实由统计信息变化引起,而不是把其他变化覆盖掉。\n\n### 5. 动态采样、SQL Plan Management 和 Hint 的边界\n\n当持久化统计不足时,优化器可能使用动态采样或其他自适应机制改善估算。它不是统计信息维护的替代品:\n\n- 动态采样有额外优化阶段开销;\n- 复杂数据分布仍可能估算错误;\n- 运行时数据变化可能使一次采样不具代表性。\n\nHint 也不是永久修复手段。Hint 直接影响某次 SQL 的优化决策,但可能因:\n\n- 表结构变化;\n- 数据分布变化;\n- 版本变化;\n- Hint 无效或被忽略;\n- SQL 文本或别名变化;\n\n而失去预期效果。\n\n生产调优应优先修正 SQL 语义、数据访问方式和统计信息;需要稳定计划时,再评估 SQL Plan Management、SQL Profile、基线或有限范围的 Hint。不同功能涉及不同许可和版本能力,不能混为一谈。\n\n---\n\n## 六、从统计信息回到实际执行计划\n\n### 1. `EXPLAIN PLAN` 不等于实际执行结果\n\n以下命令展示的是优化器为某次解析生成的计划:\n\n```sql\nEXPLAIN PLAN FOR\nSELECT o.order_id\nFROM app.orders o\nWHERE o.customer_id = 1001;\n\nSELECT *\nFROM TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +PREDICATE'));\n```\n\n它有两个限制:\n\n1. 它可能不是应用实际执行的游标;\n2. 它没有真实执行行数和真实资源消耗。\n\n更可靠的方式是查看已经执行的游标:\n\n```sql\nSELECT *\nFROM TABLE(\n DBMS_XPLAN.DISPLAY_CURSOR(\n sql_id => '9abc123def456',\n format => 'ALLSTATS LAST +PEEKED_BINDS +PREDICATE'\n )\n);\n```\n\n要看到实际行数,执行时通常需要采集执行统计,例如:\n\n```sql\nSELECT /*+ GATHER_PLAN_STATISTICS */\n o.order_id\nFROM app.orders o\nWHERE o.customer_id = 1001;\n```\n\n也可以使用适当的会话或系统级统计配置,但范围越大,额外开销越高。\n\n### 2. 重点看估算行数和实际行数\n\n执行计划中常见两组重要列:\n\n- `E-Rows`:优化器估算行数;\n- `A-Rows`:实际执行行数。\n\n假设某连接节点显示:\n\n```text\nOperation E-Rows A-Rows\nHASH JOIN 1000 2000000\nTABLE ACCESS 1000 2000000\n```\n\n这说明估算误差约为 2000 倍。此时应优先检查:\n\n- 谓词列统计信息;\n- 数据倾斜和直方图;\n- 多列相关性;\n- 隐式类型转换;\n- 绑定变量行为;\n- 分区裁剪是否发生;\n- SQL 条件是否与业务实际选择性一致。\n\n如果 `E-Rows` 与 `A-Rows` 接近,但 SQL 仍慢,问题可能在:\n\n- 单次访问量本身太大;\n- I/O 延迟;\n- CPU 资源不足;\n- 并发争用;\n- 排序或临时空间;\n- 执行次数过多。\n\n### 3. 估算正确也可能计划不适合\n\n反例:\n\n- 估算返回 100 万行;\n- 实际也返回 100 万行;\n- 但 SQL 只需要 10 个字段中的 2 个字段;\n- 当前计划通过低选择性索引逐行回表。\n\n这时统计信息可能是正确的,问题是访问路径的成本模型和物理设计不适合工作负载。可以评估:\n\n- 更合理的索引前导列;\n- 覆盖索引是否值得;\n- 全表扫描或分区扫描;\n- SQL 是否可以减少返回列和行;\n- 是否存在不必要的排序、去重或函数计算。\n\n因此“更新统计信息”不能成为所有计划问题的统一答案。\n\n---\n\n## 七、从 AWR、ASH 和执行计划建立因果链\n\n一个可靠的调优结论应至少形成如下链路:\n\n```text\n业务症状\n ↓\n时间窗口和影响范围\n ↓\nAWR:区间负载、DB time、CPU、等待类别\n ↓\nASH:具体 SQL、会话、阻塞者和时间分布\n ↓\n执行计划:访问路径、连接顺序、E-Rows/A-Rows\n ↓\n统计信息、对象结构和主机资源\n ↓\n修复措施与回归验证\n```\n\n### 示例:接口从 200 ms 变为 5 秒\n\n#### 第一步:确认数据库是否真的变慢\n\n查看该时间窗口的:\n\n- 数据库时间;\n- 前台等待时间;\n- 事务提交时间;\n- CPU;\n- 用户 I/O;\n- 活跃会话数量;\n- SQL 执行次数。\n\n如果应用响应时间增加,但数据库时间没有增加,应优先检查连接池、网络或应用线程池,而不是直接改 SQL。\n\n#### 第二步:用 ASH 定位活动来源\n\n如果 ASH 显示某 SQL 的样本突然增多,并且主要等待 `User I/O`,可以进一步查询:\n\n- 该 SQL 的执行次数是否突增;\n- 单次逻辑读和物理读是否增加;\n- 是否出现新的子游标;\n- 是否发生计划变化;\n- 对象是否刚刚装载大量数据。\n\n#### 第三步:比较实际计划\n\n使用 `DBMS_XPLAN.DISPLAY_CURSOR` 比较正常时和异常时的:\n\n- 计划哈希值;\n- `E-Rows` 与 `A-Rows`;\n- 连接顺序;\n- 索引、全表扫描和分区裁剪;\n- 临时空间;\n- 并行执行;\n- 绑定变量窥视结果。\n\n#### 第四步:判断根因\n\n可能得到不同结论:\n\n- 统计信息过期,导致基数估算错误;\n- 数据倾斜变化,原有直方图不再代表现实;\n- SQL 文本未变但子游标因环境或绑定变量产生不同计划;\n- 业务调用次数增加十倍,单次执行没有变慢;\n- 存储延迟增加,SQL 计划没有变化;\n- 事务未提交,后续请求在等待行锁。\n\n每一种结论的修复方式都不同。\n\n---\n\n## 八、容量分析:从“现在很慢”推导“还能承受多少”\n\n### 1. 容量不是单一指标\n\n数据库容量至少包含:\n\n- CPU 容量;\n- I/O 吞吐和 I/O 延迟容量;\n- 内存容量;\n- 并发会话容量;\n- 日志写入容量;\n- 锁和事务并发容量;\n- 临时空间容量;\n- 表空间和归档空间容量;\n- 连接池及网络容量。\n\n“CPU 还有 30%”不能证明系统还有 30% 的整体容量,因为 I/O、日志、锁或连接数可能已经先达到瓶颈。\n\n### 2. Little 定律与活动会话\n\n排队系统中常用 Little 定律:\n\n\\[\nL = \\lambda R\n\\]\n\n其中:\n\n- \\(L\\):系统中的平均请求数;\n- \\(\\lambda\\):吞吐率;\n- \\(R\\):平均响应时间。\n\n在数据库分析中,可以近似理解为:\n\n\\[\nAAS \\approx TPS \\times \\text{每事务平均数据库时间}\n\\]\n\n例如:\n\n- 每秒 200 个事务;\n- 每个事务平均消耗 20 ms 数据库时间。\n\n则:\n\n\\[\nAAS = 200 \\times 0.02 = 4\n\\]\n\n这表示平均有约 4 个活动会话。\n\n如果吞吐量不变,而单事务数据库时间从 20 ms 增加到 100 ms:\n\n\\[\nAAS = 200 \\times 0.1 = 20\n\\]\n\n活动会话增加五倍,连接池、锁竞争和 CPU 争用可能随之恶化。性能问题常常不是线性增长:接近资源上限后,排队时间会快速增加。\n\n### 3. 用服务需求估算资源上限\n\n设某类请求每秒到达 \\(\\lambda\\) 次,每次在某资源上平均消耗 \\(D\\) 秒,则该资源所需利用率近似为:\n\n\\[\nU = \\lambda D\n\\]\n\n例如,某 SQL 每秒执行 100 次,每次平均消耗 8 ms CPU:\n\n\\[\nU_{\\text{CPU}} = 100 \\times 0.008 = 0.8\n\\]\n\n这相当于消耗 0.8 个 CPU 核。如果数据库可用 CPU 配额只有 1 个核,CPU 已接近饱和;如果可用 8 个核,CPU 可能不是主要瓶颈。\n\n对 I/O 也可以使用类似思路。若每秒产生 500 次 I/O 请求,每次存储服务时间平均 4 ms:\n\n\\[\nU_{\\text{I/O}} = 500 \\times 0.004 = 2\n\\]\n\n这意味着单一串行服务能力不足,实际系统可能依赖多个并行服务队列;不能简单把这个结果当作设备利用率,但它说明“请求率乘以单次服务时间”已经超过单服务通道能力。\n\n### 4. 不能用平均值掩盖峰值\n\n容量模型应至少分别计算:\n\n- 工作日平均;\n- 业务高峰;\n- 批处理窗口;\n- 发布或数据装载窗口;\n- 故障降级场景;\n- 副本、备份和统计信息收集期间。\n\n例如某小时平均 TPS 为 100,但五分钟峰值 TPS 为 500。用 100 TPS 规划 CPU 和 I/O,可能在高峰时直接进入排队区。\n\nAWR 适合观察区间总量和趋势,ASH 适合定位尖峰中的活动会话,操作系统监控则用于验证主机级 CPU、内存、I/O 和网络。\n\n---\n\n## 九、表空间、临时空间和日志容量\n\n### 1. 表空间容量\n\n查看永久表空间使用情况时,应明确使用的是数据文件总大小、已使用空间还是可自动扩展上限。不同查询反映不同问题。\n\n例如可以先查看数据文件:\n\n```sql\nSELECT tablespace_name,\n file_name,\n bytes / 1024 / 1024 AS size_mb,\n autoextensible,\n maxbytes / 1024 / 1024 AS max_mb\nFROM dba_data_files\nORDER BY tablespace_name, file_name;\n```\n\n这里的 `MAXBYTES` 只是数据文件的自动扩展上限,不代表底层文件系统或 ASM 磁盘组真的有足够空间。\n\n因此容量判断至少要同时验证:\n\n- 表空间剩余可用空间;\n- 数据文件是否允许自动扩展;\n- 自动扩展上限;\n- 文件系统或 ASM 磁盘组剩余空间;\n- 增长速度;\n- 大对象、分区和索引的增长来源。\n\n“表空间还有 20%”也不一定安全。如果每天增长 50 GB,而底层只剩 10 GB,系统仍会很快失败。\n\n### 2. 临时表空间\n\n排序、哈希连接、临时结果集和某些并行操作可能使用临时表空间。检查临时文件:\n\n```sql\nSELECT tablespace_name,\n file_name,\n bytes / 1024 / 1024 AS size_mb,\n autoextensible,\n maxbytes / 1024 / 1024 AS max_mb\nFROM dba_temp_files\nORDER BY tablespace_name, file_name;\n```\n\n临时空间增长的诊断路径包括:\n\n1. 观察临时空间当前使用量;\n2. 找到消耗临时空间的会话和 SQL;\n3. 查看执行计划中的排序、哈希和并行操作;\n4. 判断是数据量增长、内存不足、计划变化还是 SQL 逻辑问题;\n5. 扩容前确认底层空间和增长上限。\n\n单纯扩大临时表空间只能延后失败,不能修复错误连接顺序或不必要排序。\n\n### 3. 归档和日志容量\n\n日志相关容量包括:\n\n- redo 日志组;\n- 归档日志空间;\n- 备份保留;\n- Data Guard 传输或应用延迟;\n- 恢复窗口要求。\n\n如果归档目的地空间耗尽,数据库可能出现严重影响甚至无法继续正常归档。容量监控必须观察增长速率和故障恢复路径,而不是只设置一个固定百分比阈值。\n\n---\n\n## 十、锁、事务和等待链\n\n等待事件经常只是阻塞链的表象。一个完整的锁问题通常是:\n\n```text\n会话 A 开启事务并修改行\n │\n │ 未提交\n ▼\n会话 B 修改同一行\n │\n ▼\nenq: TX - row lock contention\n │\n ▼\n会话 C 可能继续等待 B 或其他资源\n```\n\n诊断时应同时观察:\n\n- 阻塞会话;\n- 被阻塞会话;\n- 事务开始时间;\n- 当前 SQL;\n- 最后一次提交时间;\n- 应用请求和连接归属;\n- 回滚段和事务持续时间。\n\n不能把所有锁等待归因于“数据库锁配置不合理”。很多锁问题来自应用事务边界:\n\n- 开启事务后执行远程调用;\n- 修改数据后等待用户操作;\n- 连接归还池前未提交或回滚;\n- 批量操作过大;\n- 异常路径没有正确结束事务。\n\n修复阻塞的优先级通常是先停止或修正制造长事务的业务路径,再评估是否需要终止会话。终止会话不是无成本操作,可能触发大量回滚,并使恢复时间不可预测。\n\n---\n\n## 十一、常见误解与失败诊断\n\n### 误解一:Top Wait Event 第一名就是根因\n\n某等待事件累计时间最多,可能只是因为执行次数特别多。应同时看:\n\n\\[\n\\text{平均等待时间}\n=\n\\frac{\\text{总等待时间}}{\\text{等待次数}}\n\\]\n\n例如:\n\n- 事件 A:等待 100 万次,总计 1000 秒,平均 1 ms;\n- 事件 B:等待 1000 次,总计 500 秒,平均 500 ms。\n\n事件 A 的累计时间更高,但事件 B 可能更直接反映存储或锁的异常。\n\n### 误解二:出现物理读就应该增加内存\n\n物理读可能由以下原因造成:\n\n- SQL 扫描了过多数据;\n- 索引回表次数过多;\n- 工作集本来就超过内存;\n- 缓存被大量无效访问冲刷;\n- 存储延迟异常。\n\n增加缓存只能缓解部分问题。如果 SQL 每次都扫描一个远大于缓存的表,缓存增加不一定有效。\n\n### 误解三:执行计划用了索引就一定更快\n\n索引访问适合高选择性、访问列和数据布局匹配的场景。返回大量行时,索引逐行回表可能比顺序扫描更慢。\n\n判断依据应是:\n\n- 返回行数;\n- `E-Rows` 与 `A-Rows`;\n- 逻辑读和物理读;\n- 单次执行时间;\n- 执行次数;\n- 并发下的总资源消耗。\n\n### 误解四:重新收集统计信息一定能修复慢 SQL\n\n如果真正根因是:\n\n- 锁等待;\n- 存储延迟;\n- 提交过于频繁;\n- SQL 执行次数暴增;\n- 应用连接池排队;\n- 主机 CPU 配额不足;\n\n重新收集统计信息不仅无效,还可能改变原本稳定的执行计划。\n\n### 误解五:ASH 没有记录就说明没有问题\n\nASH 是采样系统。短 SQL、瞬时锁和采样间隔之间的事件可能漏掉。对短时尖峰,应结合:\n\n- 应用日志;\n- 实时 `V Oracle 监控与调优:AWR、ASH、等待事件、统计和容量 - WR Blog
WR Blog 加载中...
返回文章
数据库OracleAWR性能优化

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量封面

数据库基础体系 · 第 112/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Oracle 监控与调优:AWR、ASH、等待事件、统计和容量

Oracle 性能问题通常不是“某条 SQL 慢”这么简单。一次请求从客户端到数据库,可能经历连接排队、CPU 争用、逻辑读、物理读、锁等待、日志提交、并行执行、网络传输和存储响应。要判断问题,必须同时回答四个问题:

  1. 数据库当时在做什么?
  2. 时间花在哪里?
  3. 优化器为什么选择这个执行计划?
  4. 当前资源是否已经接近容量边界?

Oracle 提供了几组互补的观测机制:

  • AWR(Automatic Workload Repository):以快照为边界保存一段时间内的数据库工作负载摘要。
  • ASH(Active Session History):以活动会话为对象,记录会话在某个时间点正在运行或等待什么。
  • 等待事件(Wait Events):描述会话为什么没有继续执行。
  • 统计信息(Optimizer Statistics):描述表、列、索引等对象的数据分布,供优化器估算执行代价。
  • 容量分析:把吞吐量、响应时间、CPU、I/O、并发和数据库时间联系起来,判断系统距离瓶颈还有多少余量。

这些机制的观察对象不同。AWR 和 ASH 不能替代执行计划,等待事件也不能直接证明某个对象有问题,统计信息更不是运行时性能计数器。


一、先区分几种“时间”:响应时间、数据库时间和资源时间

1. 响应时间不等于数据库时间

一次请求的响应时间可以抽象为:

R=Rqueue+Rapplication+Rnetwork+RdatabaseR = R_{\text{queue}} + R_{\text{application}} + R_{\text{network}} + R_{\text{database}}

其中:

  • RR:用户感知的端到端响应时间;
  • RqueueR_{\text{queue}}:请求在连接池、线程池或其他队列中的等待时间;
  • RapplicationR_{\text{application}}:应用代码执行时间;
  • RnetworkR_{\text{network}}:网络传输时间;
  • RdatabaseR_{\text{database}}:数据库内部处理时间。

Oracle 主要观察数据库内部部分。即使 AWR 显示数据库负载正常,应用仍可能因为连接池耗尽或网络延迟而超时。

Oracle 中常见的 DB time 是所有前台会话处于活动状态时消耗的时间总和。活动状态通常包括:

  • 正在使用 CPU;
  • 正在等待非空闲等待事件。

如果同一时刻有 10 个会话各自活动 1 秒,DB time 大约增加 10 秒,而墙上时钟只过去 1 秒。因此:

平均活动会话数(AAS)=DB time墙上时间\text{平均活动会话数(AAS)} = \frac{\text{DB time}}{\text{墙上时间}}

AAS 是性能分析中的核心量。它回答的是:在一个时间区间内,平均有多少个前台会话正在消耗数据库资源或等待数据库资源。

2. 一个完整算例

假设某小时的 AWR 报告给出:

  • DB time:7200 秒;
  • DB CPU:3600 秒;
  • 非空闲等待时间约为 3600 秒;
  • 该区间长度:3600 秒。

则:

AAS=72003600=2AAS = \frac{7200}{3600} = 2

平均有 2 个前台会话处于活动状态。

进一步可以分解:

DB timeDB CPU+非空闲等待时间\text{DB time} \approx \text{DB CPU} + \text{非空闲等待时间}

于是:

  • CPU 占 DB time:3600/7200=50%3600 / 7200 = 50\%
  • 非空闲等待占 DB time:3600/7200=50%3600 / 7200 = 50\%

这并不表示数据库只用了 50% 的主机 CPU。DB CPU 是所有会话累计使用的 CPU 时间,而主机 CPU 使用率还受到后台进程、其他进程、CPU 核数和虚拟化调度的影响。

如果数据库运行在 8 个逻辑 CPU 上,那么 3600 秒 DB CPU 分布在 3600 秒墙上时间中,平均相当于约 1 个 CPU 核:

平均 CPU 核占用=DB CPU墙上时间=1\text{平均 CPU 核占用} = \frac{\text{DB CPU}}{\text{墙上时间}} = 1

但这仍然不能直接推出“CPU 没有问题”。如果业务高峰只有 5 分钟,峰值 CPU 可能远高于小时平均值;如果运行在受限的容器 CPU 配额中,8 个宿主机 CPU 也不代表数据库可以使用 8 个 CPU。

3. CPU 不是等待事件

Oracle 会话状态常见为:

  • ON CPU:正在 CPU 上执行;
  • WAITING:正在等待某个事件;
  • 其他状态:例如会话未处于数据库活动执行阶段。

CPU 消耗不会以一个叫作 CPU 的等待事件出现。分析时必须同时看:

  • DB CPU;
  • 前台等待时间;
  • 活跃会话数量;
  • 主机 CPU、运行队列和 CPU 配额;
  • SQL 的执行次数和单次资源消耗。

“没有明显等待事件”不代表没有性能问题,可能是 CPU 饱和、SQL 做了过多逻辑读,或者应用端没有把数据库时间记录完整。


二、AWR:以快照比较工作负载变化

1. AWR 记录什么

AWR 是 Oracle 自动工作负载资料库。它周期性记录数据库状态和工作负载摘要,典型内容包括:

  • 系统级时间模型统计;
  • 系统等待事件;
  • 前台和后台统计;
  • SQL 的执行次数、逻辑读、物理读、CPU 时间、数据库时间等;
  • 实例活动和部分资源统计;
  • 对象级访问或变更统计;
  • 相关的优化器和系统信息。

AWR 的基本数据流是:

运行中的内存统计
        │
        │ 生成快照
        ▼
AWR 持久化历史数据
        │
        │ 比较两个快照
        ▼
区间报告:增量、排名、趋势

AWR 不是实时监控工具。它记录的是快照之间的累计变化,因此一个小时的报告可能掩盖五分钟内发生的尖峰。

AWR 的诊断数据通常保存在数据库内部的相关表空间中。快照具有:

  • 快照编号;
  • 开始时间;
  • 结束时间;
  • 实例标识;
  • 数据库标识。

在 RAC 环境中,需要注意数据库级和实例级视角,查询通常需要使用 GV$ 视图或在正确的实例上生成报告。

2. 快照间隔和保留时间不是性能结论

Oracle 环境中的快照间隔和保留时间可以配置。某些安装的常见默认值是每小时生成一次并保留若干天,但具体值取决于版本、安装方式和管理员配置,不能把默认值当作规范保证。

可以通过以下查询查看配置:

SELECT snap_interval,
       retention,
       topnsql
FROM   dba_hist_wr_control;

这些字段分别描述快照间隔、保留策略和 SQL 记录范围。查询通常需要相应的数据字典权限。

在生产环境中,修改 AWR 控制参数会影响存储量和诊断能力,不应为了“让报告更详细”而无限缩短间隔或无限延长保留时间。更高的采样或保留成本需要结合问题发生频率、存储空间和合规要求评估。

3. AWR 报告的本质是“两个状态的差分”

例如,某 SQL 在快照 100 时累计:

  • 执行次数:1000;
  • DB time:500 秒;
  • 逻辑读:1,000,000。

在快照 101 时累计:

  • 执行次数:1100;
  • DB time:900 秒;
  • 逻辑读:1,800,000。

那么区间增量为:

  • 执行次数:100;
  • DB time:400 秒;
  • 逻辑读:800,000。

区间平均值为:

平均每次执行 DB time=400100=4 秒\text{平均每次执行 DB time} = \frac{400}{100} = 4\text{ 秒}

平均每次执行逻辑读=800000100=8000 次\text{平均每次执行逻辑读} = \frac{800000}{100} = 8000\text{ 次}

不能直接用累计值判断当前区间性能,否则会把数据库启动以来的历史工作负载混入结论。

4. 生成 AWR 报告

Oracle 常见环境中可以使用 SQL*Plus 提供的报告脚本:

@?/rdbms/admin/awrrpt.sql

脚本通常会依次询问:

  1. 报告类型,例如单实例或 RAC;
  2. 报告格式;
  3. 起始快照;
  4. 结束快照;
  5. 输出文件名。

它要求执行者具有足够的访问权限,并且环境必须具备相应的 AWR 能力。

报告结果应按以下顺序阅读:

  1. 报告时间范围和数据库实例;
  2. Load Profile;
  3. Top Timed Events;
  4. Time Model Statistics;
  5. SQL 统计;
  6. Instance Activity;
  7. 主机 CPU 和 I/O 信息;
  8. 与业务峰值、发布、批处理时间对照。

不要看到 Top Timed Events 的第一项就立刻修改参数。它只说明某个事件在该时间区间累计消耗较多时间,还需要结合执行次数、并发量、调用链和资源上限。

5. AWR 的许可证边界

AWR、ASH 以及相关历史诊断能力属于 Oracle Diagnostics Pack 的范围。是否可以在某个环境中使用,取决于 Oracle 版本、版本选项、部署方式和许可证合同。不能因为视图存在就默认可以在任何环境中自由使用。

在生产环境使用 AWR 报告、历史 ASH 或相关接口前,应由负责 Oracle 许可证的团队确认授权范围。本文讨论的是功能语义,不替代许可证判断。


三、ASH:回答“某一时刻哪些会话在做什么”

1. ASH 的观察单位是活动会话

ASH,即 Active Session History,面向“活动会话”采样。一个样本通常包含类似信息:

  • 样本时间;
  • 会话标识;
  • SQL 标识;
  • 会话当前是否在 CPU 上;
  • 等待事件和等待参数;
  • 等待类别;
  • 当前对象或数据文件等上下文;
  • 阻塞会话相关信息;
  • 服务名、模块、动作等会话属性。

ASH 的重要特点是:

  • 它不是每次等待开始和结束都完整记录;
  • 它主要记录活动会话;
  • 它通常以约一秒级的采样粒度观察活动;
  • 短于采样间隔的瞬时事件可能被漏采;
  • 长时间持续的等待更容易被观察到。

因此,ASH 是抽样历史,不是完整审计日志。某条 SQL 没出现在 ASH 中,不一定代表它从未执行过,可能是执行太快、未处于活动状态,或者历史数据已被覆盖。

2. 当前 ASH 与历史 ASH

当前内存中的活动会话历史可通过 V$ACTIVE_SESSION_HISTORY 查询。历史持久化数据通常通过 DBA_HIST_ACTIVE_SESS_HISTORY 查询。

一个查看最近活动会话的示例:

SELECT sample_time,
       session_id,
       session_serial#,
       session_state,
       event,
       wait_class,
       sql_id,
       module
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '10' MINUTE
ORDER BY sample_time DESC
FETCH FIRST 100 ROWS ONLY;

需要注意:

  • V$ACTIVE_SESSION_HISTORY 的可见历史受内存和当前负载影响;
  • 历史视图需要相应权限和许可;
  • FETCH FIRST 的使用要符合目标数据库版本;
  • 在 RAC 中通常应考虑 GV$ACTIVE_SESSION_HISTORY,并显示 INST_ID

按等待类别聚合:

SELECT wait_class,
       COUNT(*) AS samples
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '15' MINUTE
GROUP BY wait_class
ORDER BY samples DESC;

这里的 samples 不是等待次数,也不是精确的等待秒数。若采样间隔近似为 1 秒,可以把样本数量作为活动时间的近似,但仍然要明确这是抽样估计。

3. 用 ASH 找到“谁、何时、因为什么”

例如,某接口在 10:00 到 10:05 变慢,可以按 SQL 和等待类别分组:

SELECT sql_id,
       session_state,
       wait_class,
       event,
       COUNT(*) AS samples
FROM   v$active_session_history
WHERE  sample_time >= TO_TIMESTAMP('2025-01-01 10:00:00',
                                   'YYYY-MM-DD HH24:MI:SS')
AND    sample_time <  TO_TIMESTAMP('2025-01-01 10:05:00',
                                   'YYYY-MM-DD HH24:MI:SS')
GROUP BY sql_id, session_state, wait_class, event
ORDER BY samples DESC;

分析步骤是:

  1. 先确定时间窗口,而不是只看“最近”;
  2. 找出活动样本最多的 SQL;
  3. 区分 ON CPUWAITING
  4. 对等待样本按 wait_classevent 聚合;
  5. 回到 SQL 执行计划和业务调用次数;
  6. 检查是否存在阻塞会话、I/O 延迟或并发突增。

如果同一 SQL 的 ASH 样本主要是 ON CPU,应检查逻辑读、连接条件、排序、聚合和执行次数;如果主要是 User I/O,应继续区分物理读、读请求延迟和 SQL 是否不必要地访问了大量数据;如果主要是 ConcurrencyApplication,则应检查锁、闩锁、事务和共享资源。


四、等待事件:等待什么不等于谁有错

1. 等待事件的结构

一个等待事件通常可以理解为:

会话
 ├─ 状态:WAITING 或 ON CPU
 ├─ 事件:等待的资源或同步点
 ├─ 等待类别:User I/O、Commit、Concurrency 等
 ├─ 参数:例如文件号、块号、对象号、锁类型
 └─ 持续时间与等待次数

在当前会话视图中,可以先观察活动会话:

SELECT sid,
       serial#,
       username,
       status,
       sql_id,
       event,
       wait_class,
       state,
       seconds_in_wait,
       blocking_session,
       blocking_session_status
FROM   v$session
WHERE  status = 'ACTIVE'
ORDER BY seconds_in_wait DESC;

常见字段的含义:

  • EVENT:当前等待事件;
  • WAIT_CLASS:等待类别;
  • STATE:当前是等待、刚结束等待,还是其他状态;
  • SECONDS_IN_WAIT:当前等待相关的时间字段;
  • BLOCKING_SESSION:若已识别阻塞者,给出阻塞会话;
  • SQL_ID:当前或相关 SQL 的标识。

V$SESSION 是实时视图,适合回答“现在发生了什么”,不适合单独回答“过去一小时发生了什么”。

2. 常见等待事件的正确解释

db file sequential read

通常表示一次单块数据文件读取。它经常出现在索引访问、按 ROWID 回表或其他单块读取路径中,但事件名称不能证明“索引有问题”。

可能原因包括:

  • 索引选择性差;
  • 大量索引扫描后回表;
  • 缓存命中不足;
  • 随机访问模式;
  • SQL 估算错误导致错误的访问路径。

反例是:某 SQL 使用了索引,但返回了表中大部分行。此时索引并不一定比全表扫描好,单块读等待反而可能增加。

db file scattered read

在传统命名下常与多块读取相关,常见于全表扫描或全索引扫描。但具体行为取决于版本、访问路径和执行环境。不能仅凭事件名称认定“全表扫描就是错误”,因为对大部分数据的顺序读取可能比索引回表更合理。

direct path read

表示绕过部分缓冲区缓存路径的直接读取,可能与大表扫描、临时段读取或其他执行方式有关。需要结合 SQL 计划、对象大小、并行度和 I/O 延迟判断。

log file sync

前台会话提交时等待 LGWR 等日志写入路径完成。常见原因包括:

  • 提交过于频繁;
  • redo 生成量大;
  • 日志写入延迟;
  • 存储或虚拟化层抖动;
  • LGWR 受到 CPU 调度影响。

“出现 log file sync 就应该关闭日志”是错误做法。日志持久性是事务提交语义的重要组成部分,不能为了降低等待而破坏提交可靠性。

enq: TX - row lock contention

通常表示事务正在等待其他事务释放行锁或相关事务资源。诊断时要找到阻塞链:

SELECT sid,
       serial#,
       username,
       event,
       blocking_session,
       sql_id,
       row_wait_obj#
FROM   v$session
WHERE  blocking_session IS NOT NULL;

还应结合事务持续时间、应用请求、未提交事务和锁对象进一步确认。直接杀掉阻塞会话可能导致回滚,回滚时间又可能继续阻塞其他会话。

buffer busy waits

通常说明多个会话对某个缓冲区存在并发访问冲突,但根因可能是热点块、段头、索引叶块、块管理方式或工作负载模式。不能看到该事件就直接增加缓冲区缓存。

cursor: pin S wait on X 和共享游标相关等待

这类等待可能与游标失效、硬解析、共享池竞争、DDL 或对象状态变化有关。需要结合 SQL 版本数量、解析次数、应用是否使用绑定变量以及具体版本行为分析。

3. 等待类别是分类,不是根因

等待类别便于排序和归纳,例如:

  • User I/O:用户 SQL 直接相关的 I/O 等待;
  • System I/O:后台进程或系统级 I/O;
  • Commit:提交路径;
  • Concurrency:并发控制;
  • Application:应用级同步或锁;
  • Configuration:配置导致的等待;
  • Network:网络相关;
  • Scheduler:调度或资源管理相关。

类别只能缩小范围。真正的诊断需要把:

等待事件
+ 等待参数
+ SQL
+ 执行计划
+ 对象
+ 阻塞者
+ 主机资源
+ 时间窗口

联系起来。


五、统计信息:优化器对数据分布的模型

1. 统计信息不是运行时计数器

Oracle 优化器需要估算候选执行计划的成本。它使用的统计信息通常包括:

  • 表的行数和块数;
  • 列的最小值、最大值、非空数量;
  • 列的不同值数量;
  • 直方图;
  • 索引的层级、叶块、聚簇因子等;
  • 分区级或全局统计信息;
  • 相关的系统统计信息。

统计信息描述的是优化器看到的数据模型,不是“这条 SQL 最近运行了多少次”。

这一区分很重要:

  • DBA_TAB_STATISTICS 等视图描述对象统计信息;
  • AWR、ASH、动态性能视图描述运行时活动;
  • 执行计划描述优化器选择的访问路径;
  • 运行时执行统计描述实际行数和资源消耗。

2. 优化器为何会选错计划

设某个谓词选择率为 ss,表中行数为 NN,优化器估算返回行数:

R^=N×s\hat{R} = N \times s

如果真实返回行数为 RR,则估算误差可以用:

E=RR^E = \frac{R}{\hat{R}}

表示。

例如:

  • 表行数 N=10,000,000N = 10,000,000
  • 优化器估算选择率 s=0.001s = 0.001
  • 估算返回行数 R^=10,000\hat{R} = 10,000
  • 实际返回行数 R=2,000,000R = 2,000,000

则:

E=2,000,00010,000=200E = \frac{2,000,000}{10,000}=200

优化器以为只需要处理 1 万行,实际要处理 200 万行。它可能选择索引加回表,而实际全表扫描或其他批量访问路径更合适。

这就是为什么执行计划调优不能只盯着“是否使用索引”。关键问题是:

  1. 基数估算是否接近真实值;
  2. 访问路径是否匹配返回数据量;
  3. 连接顺序和连接方式是否合理;
  4. 排序、聚合、临时空间和并行度是否合适。

3. 直方图解决什么问题

如果列值分布均匀,优化器可用不同值数量近似估算选择率。但现实中可能存在倾斜:

status = 'ACTIVE'   99%
status = 'DELETED'   1%

如果没有足够的分布信息,优化器可能对两个值使用相近的选择率估计。直方图用于表达列值分布,使不同谓词得到不同估算。

但直方图不是越多越好:

  • 它增加统计信息维护复杂度;
  • 数据分布变化后可能过期;
  • 绑定变量和谓词值的变化可能导致不同计划;
  • 重新收集统计信息可能触发计划变化。

可以查看表和列统计信息:

SELECT owner,
       table_name,
       num_rows,
       blocks,
       last_analyzed,
       stale_stats
FROM   dba_tab_statistics
WHERE  owner = 'APP'
AND    table_name = 'ORDERS';

SELECT owner,
       table_name,
       column_name,
       num_distinct,
       num_nulls,
       histogram,
       last_analyzed
FROM   dba_tab_col_statistics
WHERE  owner = 'APP'
AND    table_name = 'ORDERS';

NUM_ROWSBLOCKS 是统计信息中的估计值,不应直接当作实时精确值。

4. 收集统计信息

典型示例:

BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(
      ownname          => 'APP',
      tabname          => 'ORDERS',
      estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
      method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
      cascade          => DBMS_STATS.AUTO_CASCADE,
      no_invalidate    => DBMS_STATS.AUTO_INVALIDATE
  );
END;
/

这些参数的意图是:

  • AUTO_SAMPLE_SIZE:让 Oracle 选择采样策略;
  • SIZE AUTO:根据需要决定直方图;
  • AUTO_CASCADE:由 Oracle 决定是否处理相关索引统计;
  • AUTO_INVALIDATE:由 Oracle 决定游标失效策略。

但“自动”不等于不会产生风险。收集统计信息可能导致:

  • 优化器重新选择计划;
  • 硬解析或游标重新验证;
  • 统计信息写入占用 CPU、I/O 和临时空间;
  • 大表收集时间较长;
  • 业务高峰期间资源竞争。

因此应在业务低峰执行,并在执行前后保存:

  • SQL ID;
  • 执行计划;
  • SQL 资源指标;
  • 统计信息时间;
  • 业务响应时间。

如果需要恢复,Oracle 提供统计信息历史能力和相应的恢复接口,但前提是历史尚未过期且权限、保留策略和对象范围都满足要求。恢复统计信息之前,应确认问题确实由统计信息变化引起,而不是把其他变化覆盖掉。

5. 动态采样、SQL Plan Management 和 Hint 的边界

当持久化统计不足时,优化器可能使用动态采样或其他自适应机制改善估算。它不是统计信息维护的替代品:

  • 动态采样有额外优化阶段开销;
  • 复杂数据分布仍可能估算错误;
  • 运行时数据变化可能使一次采样不具代表性。

Hint 也不是永久修复手段。Hint 直接影响某次 SQL 的优化决策,但可能因:

  • 表结构变化;
  • 数据分布变化;
  • 版本变化;
  • Hint 无效或被忽略;
  • SQL 文本或别名变化;

而失去预期效果。

生产调优应优先修正 SQL 语义、数据访问方式和统计信息;需要稳定计划时,再评估 SQL Plan Management、SQL Profile、基线或有限范围的 Hint。不同功能涉及不同许可和版本能力,不能混为一谈。


六、从统计信息回到实际执行计划

1. EXPLAIN PLAN 不等于实际执行结果

以下命令展示的是优化器为某次解析生成的计划:

EXPLAIN PLAN FOR
SELECT o.order_id
FROM   app.orders o
WHERE  o.customer_id = 1001;

SELECT *
FROM   TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +PREDICATE'));

它有两个限制:

  1. 它可能不是应用实际执行的游标;
  2. 它没有真实执行行数和真实资源消耗。

更可靠的方式是查看已经执行的游标:

SELECT *
FROM   TABLE(
  DBMS_XPLAN.DISPLAY_CURSOR(
      sql_id          => '9abc123def456',
      format          => 'ALLSTATS LAST +PEEKED_BINDS +PREDICATE'
  )
);

要看到实际行数,执行时通常需要采集执行统计,例如:

SELECT /*+ GATHER_PLAN_STATISTICS */
       o.order_id
FROM   app.orders o
WHERE  o.customer_id = 1001;

也可以使用适当的会话或系统级统计配置,但范围越大,额外开销越高。

2. 重点看估算行数和实际行数

执行计划中常见两组重要列:

  • E-Rows:优化器估算行数;
  • A-Rows:实际执行行数。

假设某连接节点显示:

Operation              E-Rows        A-Rows
HASH JOIN              1000          2000000
TABLE ACCESS           1000          2000000

这说明估算误差约为 2000 倍。此时应优先检查:

  • 谓词列统计信息;
  • 数据倾斜和直方图;
  • 多列相关性;
  • 隐式类型转换;
  • 绑定变量行为;
  • 分区裁剪是否发生;
  • SQL 条件是否与业务实际选择性一致。

如果 E-RowsA-Rows 接近,但 SQL 仍慢,问题可能在:

  • 单次访问量本身太大;
  • I/O 延迟;
  • CPU 资源不足;
  • 并发争用;
  • 排序或临时空间;
  • 执行次数过多。

3. 估算正确也可能计划不适合

反例:

  • 估算返回 100 万行;
  • 实际也返回 100 万行;
  • 但 SQL 只需要 10 个字段中的 2 个字段;
  • 当前计划通过低选择性索引逐行回表。

这时统计信息可能是正确的,问题是访问路径的成本模型和物理设计不适合工作负载。可以评估:

  • 更合理的索引前导列;
  • 覆盖索引是否值得;
  • 全表扫描或分区扫描;
  • SQL 是否可以减少返回列和行;
  • 是否存在不必要的排序、去重或函数计算。

因此“更新统计信息”不能成为所有计划问题的统一答案。


七、从 AWR、ASH 和执行计划建立因果链

一个可靠的调优结论应至少形成如下链路:

业务症状
  ↓
时间窗口和影响范围
  ↓
AWR:区间负载、DB time、CPU、等待类别
  ↓
ASH:具体 SQL、会话、阻塞者和时间分布
  ↓
执行计划:访问路径、连接顺序、E-Rows/A-Rows
  ↓
统计信息、对象结构和主机资源
  ↓
修复措施与回归验证

示例:接口从 200 ms 变为 5 秒

第一步:确认数据库是否真的变慢

查看该时间窗口的:

  • 数据库时间;
  • 前台等待时间;
  • 事务提交时间;
  • CPU;
  • 用户 I/O;
  • 活跃会话数量;
  • SQL 执行次数。

如果应用响应时间增加,但数据库时间没有增加,应优先检查连接池、网络或应用线程池,而不是直接改 SQL。

第二步:用 ASH 定位活动来源

如果 ASH 显示某 SQL 的样本突然增多,并且主要等待 User I/O,可以进一步查询:

  • 该 SQL 的执行次数是否突增;
  • 单次逻辑读和物理读是否增加;
  • 是否出现新的子游标;
  • 是否发生计划变化;
  • 对象是否刚刚装载大量数据。

第三步:比较实际计划

使用 DBMS_XPLAN.DISPLAY_CURSOR 比较正常时和异常时的:

  • 计划哈希值;
  • E-RowsA-Rows
  • 连接顺序;
  • 索引、全表扫描和分区裁剪;
  • 临时空间;
  • 并行执行;
  • 绑定变量窥视结果。

第四步:判断根因

可能得到不同结论:

  • 统计信息过期,导致基数估算错误;
  • 数据倾斜变化,原有直方图不再代表现实;
  • SQL 文本未变但子游标因环境或绑定变量产生不同计划;
  • 业务调用次数增加十倍,单次执行没有变慢;
  • 存储延迟增加,SQL 计划没有变化;
  • 事务未提交,后续请求在等待行锁。

每一种结论的修复方式都不同。


八、容量分析:从“现在很慢”推导“还能承受多少”

1. 容量不是单一指标

数据库容量至少包含:

  • CPU 容量;
  • I/O 吞吐和 I/O 延迟容量;
  • 内存容量;
  • 并发会话容量;
  • 日志写入容量;
  • 锁和事务并发容量;
  • 临时空间容量;
  • 表空间和归档空间容量;
  • 连接池及网络容量。

“CPU 还有 30%”不能证明系统还有 30% 的整体容量,因为 I/O、日志、锁或连接数可能已经先达到瓶颈。

2. Little 定律与活动会话

排队系统中常用 Little 定律:

L=λRL = \lambda R

其中:

  • LL:系统中的平均请求数;
  • λ\lambda:吞吐率;
  • RR:平均响应时间。

在数据库分析中,可以近似理解为:

AASTPS×每事务平均数据库时间AAS \approx TPS \times \text{每事务平均数据库时间}

例如:

  • 每秒 200 个事务;
  • 每个事务平均消耗 20 ms 数据库时间。

则:

AAS=200×0.02=4AAS = 200 \times 0.02 = 4

这表示平均有约 4 个活动会话。

如果吞吐量不变,而单事务数据库时间从 20 ms 增加到 100 ms:

AAS=200×0.1=20AAS = 200 \times 0.1 = 20

活动会话增加五倍,连接池、锁竞争和 CPU 争用可能随之恶化。性能问题常常不是线性增长:接近资源上限后,排队时间会快速增加。

3. 用服务需求估算资源上限

设某类请求每秒到达 λ\lambda 次,每次在某资源上平均消耗 DD 秒,则该资源所需利用率近似为:

U=λDU = \lambda D

例如,某 SQL 每秒执行 100 次,每次平均消耗 8 ms CPU:

UCPU=100×0.008=0.8U_{\text{CPU}} = 100 \times 0.008 = 0.8

这相当于消耗 0.8 个 CPU 核。如果数据库可用 CPU 配额只有 1 个核,CPU 已接近饱和;如果可用 8 个核,CPU 可能不是主要瓶颈。

对 I/O 也可以使用类似思路。若每秒产生 500 次 I/O 请求,每次存储服务时间平均 4 ms:

UI/O=500×0.004=2U_{\text{I/O}} = 500 \times 0.004 = 2

这意味着单一串行服务能力不足,实际系统可能依赖多个并行服务队列;不能简单把这个结果当作设备利用率,但它说明“请求率乘以单次服务时间”已经超过单服务通道能力。

4. 不能用平均值掩盖峰值

容量模型应至少分别计算:

  • 工作日平均;
  • 业务高峰;
  • 批处理窗口;
  • 发布或数据装载窗口;
  • 故障降级场景;
  • 副本、备份和统计信息收集期间。

例如某小时平均 TPS 为 100,但五分钟峰值 TPS 为 500。用 100 TPS 规划 CPU 和 I/O,可能在高峰时直接进入排队区。

AWR 适合观察区间总量和趋势,ASH 适合定位尖峰中的活动会话,操作系统监控则用于验证主机级 CPU、内存、I/O 和网络。


九、表空间、临时空间和日志容量

1. 表空间容量

查看永久表空间使用情况时,应明确使用的是数据文件总大小、已使用空间还是可自动扩展上限。不同查询反映不同问题。

例如可以先查看数据文件:

SELECT tablespace_name,
       file_name,
       bytes / 1024 / 1024 AS size_mb,
       autoextensible,
       maxbytes / 1024 / 1024 AS max_mb
FROM   dba_data_files
ORDER BY tablespace_name, file_name;

这里的 MAXBYTES 只是数据文件的自动扩展上限,不代表底层文件系统或 ASM 磁盘组真的有足够空间。

因此容量判断至少要同时验证:

  • 表空间剩余可用空间;
  • 数据文件是否允许自动扩展;
  • 自动扩展上限;
  • 文件系统或 ASM 磁盘组剩余空间;
  • 增长速度;
  • 大对象、分区和索引的增长来源。

“表空间还有 20%”也不一定安全。如果每天增长 50 GB,而底层只剩 10 GB,系统仍会很快失败。

2. 临时表空间

排序、哈希连接、临时结果集和某些并行操作可能使用临时表空间。检查临时文件:

SELECT tablespace_name,
       file_name,
       bytes / 1024 / 1024 AS size_mb,
       autoextensible,
       maxbytes / 1024 / 1024 AS max_mb
FROM   dba_temp_files
ORDER BY tablespace_name, file_name;

临时空间增长的诊断路径包括:

  1. 观察临时空间当前使用量;
  2. 找到消耗临时空间的会话和 SQL;
  3. 查看执行计划中的排序、哈希和并行操作;
  4. 判断是数据量增长、内存不足、计划变化还是 SQL 逻辑问题;
  5. 扩容前确认底层空间和增长上限。

单纯扩大临时表空间只能延后失败,不能修复错误连接顺序或不必要排序。

3. 归档和日志容量

日志相关容量包括:

  • redo 日志组;
  • 归档日志空间;
  • 备份保留;
  • Data Guard 传输或应用延迟;
  • 恢复窗口要求。

如果归档目的地空间耗尽,数据库可能出现严重影响甚至无法继续正常归档。容量监控必须观察增长速率和故障恢复路径,而不是只设置一个固定百分比阈值。


十、锁、事务和等待链

等待事件经常只是阻塞链的表象。一个完整的锁问题通常是:

会话 A 开启事务并修改行
        │
        │ 未提交
        ▼
会话 B 修改同一行
        │
        ▼
enq: TX - row lock contention
        │
        ▼
会话 C 可能继续等待 B 或其他资源

诊断时应同时观察:

  • 阻塞会话;
  • 被阻塞会话;
  • 事务开始时间;
  • 当前 SQL;
  • 最后一次提交时间;
  • 应用请求和连接归属;
  • 回滚段和事务持续时间。

不能把所有锁等待归因于“数据库锁配置不合理”。很多锁问题来自应用事务边界:

  • 开启事务后执行远程调用;
  • 修改数据后等待用户操作;
  • 连接归还池前未提交或回滚;
  • 批量操作过大;
  • 异常路径没有正确结束事务。

修复阻塞的优先级通常是先停止或修正制造长事务的业务路径,再评估是否需要终止会话。终止会话不是无成本操作,可能触发大量回滚,并使恢复时间不可预测。


十一、常见误解与失败诊断

误解一:Top Wait Event 第一名就是根因

某等待事件累计时间最多,可能只是因为执行次数特别多。应同时看:

平均等待时间=总等待时间等待次数\text{平均等待时间} = \frac{\text{总等待时间}}{\text{等待次数}}

例如:

  • 事件 A:等待 100 万次,总计 1000 秒,平均 1 ms;
  • 事件 B:等待 1000 次,总计 500 秒,平均 500 ms。

事件 A 的累计时间更高,但事件 B 可能更直接反映存储或锁的异常。

误解二:出现物理读就应该增加内存

物理读可能由以下原因造成:

  • SQL 扫描了过多数据;
  • 索引回表次数过多;
  • 工作集本来就超过内存;
  • 缓存被大量无效访问冲刷;
  • 存储延迟异常。

增加缓存只能缓解部分问题。如果 SQL 每次都扫描一个远大于缓存的表,缓存增加不一定有效。

误解三:执行计划用了索引就一定更快

索引访问适合高选择性、访问列和数据布局匹配的场景。返回大量行时,索引逐行回表可能比顺序扫描更慢。

判断依据应是:

  • 返回行数;
  • E-RowsA-Rows
  • 逻辑读和物理读;
  • 单次执行时间;
  • 执行次数;
  • 并发下的总资源消耗。

误解四:重新收集统计信息一定能修复慢 SQL

如果真正根因是:

  • 锁等待;
  • 存储延迟;
  • 提交过于频繁;
  • SQL 执行次数暴增;
  • 应用连接池排队;
  • 主机 CPU 配额不足;

重新收集统计信息不仅无效,还可能改变原本稳定的执行计划。

误解五:ASH 没有记录就说明没有问题

ASH 是采样系统。短 SQL、瞬时锁和采样间隔之间的事件可能漏掉。对短时尖峰,应结合:

  • 应用日志;
  • 实时 V$ 视图;
  • SQL 监控能力;
  • 操作系统监控;
  • 业务时间戳;
  • 数据库审计或专门追踪机制。

不能把 ASH 当作完整事件日志。

误解六:EXPLAIN PLAN 就是应用实际运行的计划

实际游标可能因以下原因不同:

  • 绑定变量不同;
  • 子游标不同;
  • 会话环境不同;
  • 统计信息变化;
  • SQL Plan Management;
  • 自适应游标行为;
  • 对象或分区状态变化。

调优时应优先检查实际游标和实际执行统计。


十二、一个可重复的生产诊断流程

第一步:固定事实边界

记录:

  • 数据库版本和补丁;
  • 单实例还是 RAC;
  • 数据库时间、主机时间和应用时间是否一致;
  • 问题开始和结束时间;
  • 受影响的接口、用户和 SQL;
  • 是否发生发布、统计信息收集、批处理、备份或数据装载。

时间边界错误会导致 AWR、ASH 和应用日志互相对不上。

第二步:判断是数据库整体问题还是局部 SQL 问题

先看:

  • DB time 是否上升;
  • AAS 是否上升;
  • DB CPU 是否上升;
  • 等待类别是否改变;
  • 并发请求是否改变。

若只有一个 SQL 变慢,重点转向执行计划、统计信息和对象访问。若大量 SQL 同时变慢,重点检查 CPU、I/O、日志、锁、网络、存储和资源管理。

第三步:用 AWR 看区间差异

比较正常区间与异常区间:

  • 业务吞吐是否变化;
  • 每秒逻辑读、物理读、redo 和事务数;
  • DB time 每秒;
  • DB CPU 每秒;
  • 主要等待事件;
  • SQL 排名变化;
  • 主机资源变化。

必须看“每秒”或“每次执行”的归一化指标,不能只看总量。

第四步:用 ASH 定位并发来源

观察:

  • 哪些 SQL 占用活动样本最多;
  • 哪些模块或服务产生活动;
  • 是 CPU 还是等待;
  • 是否形成阻塞链;
  • 问题是否集中在某个实例;
  • 是否存在计划切换。

第五步:检查执行计划和统计信息

对候选 SQL:

  1. 找到实际 SQL_ID
  2. 查看子游标;
  3. 查看实际执行计划;
  4. 对比 E-RowsA-Rows
  5. 查看谓词是否发生隐式转换;
  6. 检查表、列、索引统计信息;
  7. 核对分区裁剪和连接条件;
  8. 评估修复后的回归风险。

第六步:验证修复而不是只验证命令成功

修复后的验证应包括:

  • 同样时间窗口的 AAS;
  • 单次执行 DB time;
  • CPU、逻辑读、物理读;
  • 等待事件和平均等待时间;
  • 执行计划是否符合预期;
  • 业务响应时间;
  • 并发高峰表现;
  • 回滚或恢复方案是否可用。

“统计信息收集成功”“索引创建成功”只说明操作完成,不说明性能问题已经解决。


十三、监控查询示例:把指标放到同一张图上

下面的查询用于查看某段时间内 ASH 按 SQL 聚合的活动样本:

SELECT sql_id,
       COUNT(*) AS active_samples,
       SUM(CASE WHEN session_state = 'ON CPU' THEN 1 ELSE 0 END)
         AS on_cpu_samples,
       SUM(CASE WHEN session_state = 'WAITING' THEN 1 ELSE 0 END)
         AS waiting_samples
FROM   v$active_session_history
WHERE  sample_time >= SYSTIMESTAMP - INTERVAL '30' MINUTE
GROUP BY sql_id
ORDER BY active_samples DESC;

解释:

  • active_samples:该 SQL 出现在活动会话采样中的次数;
  • on_cpu_samples:近似反映 CPU 活动;
  • waiting_samples:近似反映等待活动;
  • 这些值不是精确执行次数,也不是精确等待调用次数。

若要查看当前系统级等待:

SELECT event,
       wait_class,
       total_waits,
       time_waited_micro / 1000000 AS time_waited_seconds
FROM   v$system_event
WHERE  wait_class <> 'Idle'
ORDER BY time_waited_micro DESC;

系统累计视图必须结合启动时间或快照差分使用。数据库运行了很久时,累计总量很大并不等于最近发生了异常。

查看数据库启动时间:

SELECT startup_time
FROM   v$instance;

如果没有 AWR,也可以在两个时间点采集动态性能视图并自行计算增量,但这不等同于 AWR 的完整历史能力,且采集过程本身需要设计好权限、频率和存储。


十四、监控系统应如何保存指标

生产监控不应只保存一个“数据库 CPU 百分比”。至少需要保存以下维度:

数据库层

  • DB time;
  • DB CPU;
  • AAS;
  • 每秒执行次数;
  • 每次执行逻辑读、物理读、CPU 时间;
  • 提交和回滚次数;
  • redo 生成速率;
  • 等待类别和主要等待事件;
  • 阻塞会话数量;
  • 长事务;
  • 临时空间使用;
  • 表空间和归档空间增长。

SQL 层

  • SQL_ID
  • 计划哈希值;
  • 执行次数;
  • 平均和分位响应时间;
  • CPU 时间;
  • DB time;
  • 逻辑读和物理读;
  • 返回行数;
  • 子游标数量;
  • 计划切换时间。

主机和存储层

  • CPU 使用率和运行队列;
  • 可用内存和交换;
  • IOPS;
  • 吞吐;
  • I/O 延迟;
  • 文件系统或 ASM 容量;
  • 网络延迟和丢包;
  • 虚拟机或容器 CPU、I/O 配额。

指标必须带有时间窗口、实例、服务、模块和环境标签。否则同一个 SQL 在不同实例、不同服务和不同业务路径下的行为会被混在一起。


十五、生产取舍:观测能力本身也有成本

AWR、ASH、执行统计和追踪会消耗:

  • CPU;
  • 内存;
  • I/O;
  • 数据字典空间;
  • 日志和报告存储;
  • 运维人员的分析时间。

因此应区分三种场景:

日常监控

使用低开销的系统指标、应用指标、连接和事务指标,发现趋势和 SLO 违约。

问题定位

在确定时间窗口内使用 AWR、ASH、实时视图和实际执行计划,减少全库范围的长期高成本采集。

深度实验

在可回滚、可隔离的环境中启用更详细的执行统计或 SQL 追踪,验证假设后再推广到生产。

调优不是把所有诊断开关永久打开,而是在足够观测和可接受开销之间取得平衡。


Oracle 性能分析的核心不是记住某个等待事件对应某个参数,而是建立可验证的因果关系:

业务吞吐数据库时间CPU 与等待具体 SQL 和会话执行计划与统计信息资源容量和并发边界\text{业务吞吐} \rightarrow \text{数据库时间} \rightarrow \text{CPU 与等待} \rightarrow \text{具体 SQL 和会话} \rightarrow \text{执行计划与统计信息} \rightarrow \text{资源容量和并发边界}

AWR 提供区间视角,ASH 提供活动会话视角,等待事件提供资源等待视角,统计信息和执行计划提供优化器视角,容量模型则把这些结果转化为对峰值、增长和故障余量的判断。只有把这些视角放在同一条时间线上,调优结论才不会停留在“看到一个事件就修改一个参数”。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论

0 条讨论
0/1000
还没有评论,来聊聊你的看法
视图;\n- SQL 监控能力;\n- 操作系统监控;\n- 业务时间戳;\n- 数据库审计或专门追踪机制。\n\n不能把 ASH 当作完整事件日志。\n\n### 误解六:`EXPLAIN PLAN` 就是应用实际运行的计划\n\n实际游标可能因以下原因不同:\n\n- 绑定变量不同;\n- 子游标不同;\n- 会话环境不同;\n- 统计信息变化;\n- SQL Plan Management;\n- 自适应游标行为;\n- 对象或分区状态变化。\n\n调优时应优先检查实际游标和实际执行统计。\n\n---\n\n## 十二、一个可重复的生产诊断流程\n\n### 第一步:固定事实边界\n\n记录:\n\n- 数据库版本和补丁;\n- 单实例还是 RAC;\n- 数据库时间、主机时间和应用时间是否一致;\n- 问题开始和结束时间;\n- 受影响的接口、用户和 SQL;\n- 是否发生发布、统计信息收集、批处理、备份或数据装载。\n\n时间边界错误会导致 AWR、ASH 和应用日志互相对不上。\n\n### 第二步:判断是数据库整体问题还是局部 SQL 问题\n\n先看:\n\n- DB time 是否上升;\n- AAS 是否上升;\n- DB CPU 是否上升;\n- 等待类别是否改变;\n- 并发请求是否改变。\n\n若只有一个 SQL 变慢,重点转向执行计划、统计信息和对象访问。若大量 SQL 同时变慢,重点检查 CPU、I/O、日志、锁、网络、存储和资源管理。\n\n### 第三步:用 AWR 看区间差异\n\n比较正常区间与异常区间:\n\n- 业务吞吐是否变化;\n- 每秒逻辑读、物理读、redo 和事务数;\n- DB time 每秒;\n- DB CPU 每秒;\n- 主要等待事件;\n- SQL 排名变化;\n- 主机资源变化。\n\n必须看“每秒”或“每次执行”的归一化指标,不能只看总量。\n\n### 第四步:用 ASH 定位并发来源\n\n观察:\n\n- 哪些 SQL 占用活动样本最多;\n- 哪些模块或服务产生活动;\n- 是 CPU 还是等待;\n- 是否形成阻塞链;\n- 问题是否集中在某个实例;\n- 是否存在计划切换。\n\n### 第五步:检查执行计划和统计信息\n\n对候选 SQL:\n\n1. 找到实际 `SQL_ID`;\n2. 查看子游标;\n3. 查看实际执行计划;\n4. 对比 `E-Rows` 和 `A-Rows`;\n5. 查看谓词是否发生隐式转换;\n6. 检查表、列、索引统计信息;\n7. 核对分区裁剪和连接条件;\n8. 评估修复后的回归风险。\n\n### 第六步:验证修复而不是只验证命令成功\n\n修复后的验证应包括:\n\n- 同样时间窗口的 AAS;\n- 单次执行 DB time;\n- CPU、逻辑读、物理读;\n- 等待事件和平均等待时间;\n- 执行计划是否符合预期;\n- 业务响应时间;\n- 并发高峰表现;\n- 回滚或恢复方案是否可用。\n\n“统计信息收集成功”“索引创建成功”只说明操作完成,不说明性能问题已经解决。\n\n---\n\n## 十三、监控查询示例:把指标放到同一张图上\n\n下面的查询用于查看某段时间内 ASH 按 SQL 聚合的活动样本:\n\n```sql\nSELECT sql_id,\n COUNT(*) AS active_samples,\n SUM(CASE WHEN session_state = 'ON CPU' THEN 1 ELSE 0 END)\n AS on_cpu_samples,\n SUM(CASE WHEN session_state = 'WAITING' THEN 1 ELSE 0 END)\n AS waiting_samples\nFROM v$active_session_history\nWHERE sample_time >= SYSTIMESTAMP - INTERVAL '30' MINUTE\nGROUP BY sql_id\nORDER BY active_samples DESC;\n```\n\n解释:\n\n- `active_samples`:该 SQL 出现在活动会话采样中的次数;\n- `on_cpu_samples`:近似反映 CPU 活动;\n- `waiting_samples`:近似反映等待活动;\n- 这些值不是精确执行次数,也不是精确等待调用次数。\n\n若要查看当前系统级等待:\n\n```sql\nSELECT event,\n wait_class,\n total_waits,\n time_waited_micro / 1000000 AS time_waited_seconds\nFROM v$system_event\nWHERE wait_class \u003c> 'Idle'\nORDER BY time_waited_micro DESC;\n```\n\n系统累计视图必须结合启动时间或快照差分使用。数据库运行了很久时,累计总量很大并不等于最近发生了异常。\n\n查看数据库启动时间:\n\n```sql\nSELECT startup_time\nFROM v$instance;\n```\n\n如果没有 AWR,也可以在两个时间点采集动态性能视图并自行计算增量,但这不等同于 AWR 的完整历史能力,且采集过程本身需要设计好权限、频率和存储。\n\n---\n\n## 十四、监控系统应如何保存指标\n\n生产监控不应只保存一个“数据库 CPU 百分比”。至少需要保存以下维度:\n\n### 数据库层\n\n- DB time;\n- DB CPU;\n- AAS;\n- 每秒执行次数;\n- 每次执行逻辑读、物理读、CPU 时间;\n- 提交和回滚次数;\n- redo 生成速率;\n- 等待类别和主要等待事件;\n- 阻塞会话数量;\n- 长事务;\n- 临时空间使用;\n- 表空间和归档空间增长。\n\n### SQL 层\n\n- `SQL_ID`;\n- 计划哈希值;\n- 执行次数;\n- 平均和分位响应时间;\n- CPU 时间;\n- DB time;\n- 逻辑读和物理读;\n- 返回行数;\n- 子游标数量;\n- 计划切换时间。\n\n### 主机和存储层\n\n- CPU 使用率和运行队列;\n- 可用内存和交换;\n- IOPS;\n- 吞吐;\n- I/O 延迟;\n- 文件系统或 ASM 容量;\n- 网络延迟和丢包;\n- 虚拟机或容器 CPU、I/O 配额。\n\n指标必须带有时间窗口、实例、服务、模块和环境标签。否则同一个 SQL 在不同实例、不同服务和不同业务路径下的行为会被混在一起。\n\n---\n\n## 十五、生产取舍:观测能力本身也有成本\n\nAWR、ASH、执行统计和追踪会消耗:\n\n- CPU;\n- 内存;\n- I/O;\n- 数据字典空间;\n- 日志和报告存储;\n- 运维人员的分析时间。\n\n因此应区分三种场景:\n\n### 日常监控\n\n使用低开销的系统指标、应用指标、连接和事务指标,发现趋势和 SLO 违约。\n\n### 问题定位\n\n在确定时间窗口内使用 AWR、ASH、实时视图和实际执行计划,减少全库范围的长期高成本采集。\n\n### 深度实验\n\n在可回滚、可隔离的环境中启用更详细的执行统计或 SQL 追踪,验证假设后再推广到生产。\n\n调优不是把所有诊断开关永久打开,而是在足够观测和可接受开销之间取得平衡。\n\n---\n\nOracle 性能分析的核心不是记住某个等待事件对应某个参数,而是建立可验证的因果关系:\n\n\\[\n\\text{业务吞吐}\n\\rightarrow\n\\text{数据库时间}\n\\rightarrow\n\\text{CPU 与等待}\n\\rightarrow\n\\text{具体 SQL 和会话}\n\\rightarrow\n\\text{执行计划与统计信息}\n\\rightarrow\n\\text{资源容量和并发边界}\n\\]\n\nAWR 提供区间视角,ASH 提供活动会话视角,等待事件提供资源等待视角,统计信息和执行计划提供优化器视角,容量模型则把这些结果转化为对峰值、增长和故障余量的判断。只有把这些视角放在同一条时间线上,调优结论才不会停留在“看到一个事件就修改一个参数”。\n\n---\n\n## 系列导航与关联阅读\n\n- 系列入口:[数据库完整学习路线:从关系模型、事务索引到分布式与向量检索](https://wrblog.cn/articles/e04c40d6-ba22-5c0c-8442-2252df05d216)\n- 上一篇:[Oracle 安全治理:用户、角色、Profile、TDE、审计和最小权限](https://wrblog.cn/articles/0bafcd71-6809-5a8c-82de-6b203ca687bd)\n- 下一篇:[Oracle Data Pump 与迁移:导出导入、字符集、校验和停机窗口](https://wrblog.cn/articles/27f977bd-32a9-53c6-8930-e66dac19e963)\n- 延伸:[Oracle 索引与优化器:统计信息、执行计划、Hint 和 SQL 调优](https://wrblog.cn/articles/12c91cb6-fc93-5b85-a4ec-1a8a1d8fd239)\n- 延伸:[数据库可观测性:连接、事务、锁、执行计划、复制和 SLO](https://wrblog.cn/articles/7f359a1c-7d52-5c32-a46d-de6af02ca6bd)\n\n## 官方资料\n\n- [Oracle Database Documentation](https://docs.oracle.com/en/database/oracle/oracle-database/)\n- [Oracle Database Concepts](https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/)\n\n> 本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。\n","tags":["数据库","Oracle","AWR","性能优化"],"likeCount":0,"commentCount":0,"createdByUserId":"10000000000","createdByDisplayName":"小郝","createdByAvatar":"/public/profile/10000000000/avatar/2026/08/04/db02b81c-42f2-441b-8a80-61370cdbb581.webp","publishTime":"2026-09-01 12:47:19","updateTime":"2026-09-01 12:47:19"}},"status":200,"locale":"zh-CN","theme":"light"}