不能只查ASH而不加条件,因其为每秒采样的滚动内存表,默认仅保留约1小时数据,且含后台进程、网络等待等干扰项,不加时间过滤、session_type和wait_class约束会导致结果失真。
直接看 v$active_session_history 的 wait_class 字段就能定位 sql 的 i/o 等待分布,但必须加时间过滤和会话上下文约束,否则结果不可信。
为什么不能只查 ASH 表而不加条件
ASH 是每秒采样一次的滚动内存表,v$active_session_history 默认只保留最近约 1 小时(实际取决于 ASH buffer 大小和负载),且不包含空闲等待(wait_class = 'Idle' 被自动过滤)。如果直接 SELECT * FROM v$active_session_history WHERE sql_id = 'xxx',可能:
- 拿到的是已刷出内存的老数据(sample_time 超出保留窗口)
- 混入后台进程或非前台会话(session_type != 'FOREGROUND')
- 把 SQL*Net message from client 这类网络等待误判为 I/O 问题
- 忽略了 current_obj# 为空导致对象级归因失败
怎么写一条靠谱的 I/O 等待 SQL 分布查询
目标是:锁定某条 sql_id 在指定时间段内,真正由用户 I/O 引发的等待占比。关键动作包括:
- 限定 sample_time > SYSDATE - 1/24(最近 1 小时)或更窄
- 加 session_type = 'FOREGROUND' 排除后台进程干扰
- wait_class IN ('User I/O', 'System I/O') 精准聚焦 I/O 类别
- event NOT LIKE 'SQL%' 剔除网络类伪 I/O
- 关联 dba_objects 补全 object_name(需 current_obj# > 0)
示例语句:
SELECT sql_id, wait_class, event, COUNT(*) cnt,
ROUND(RATIO_TO_REPORT(COUNT(*)) OVER() * 100, 2) pct
FROM v$active_session_history a
WHERE sql_id = 'c1x9m5k7n8v2q'
AND sample_time > SYSDATE - 1/48
AND session_type = 'FOREGROUND'
AND wait_class IN ('User I/O', 'System I/O')
AND event NOT LIKE 'SQL%Net%'
GROUP BY sql_id, wait_class, event
ORDER BY cnt DESC;
WAIT_CLASS = 'User I/O' 下哪些 event 真正代表磁盘读写
不是所有标为 User I/O 的事件都等于“慢磁盘”,得看具体 event 名称:
- db file sequential read:单块读,常对应索引查找、ROWID 访问,平均等待时间高说明索引块离散或缓存不足
- db file scattered read:多块读,典型全表扫描,若单次等待长(>10ms),大概率是存储响应慢或 buffer cache 不够
- direct path read:直接路径读(如并行查询、大表排序),绕过 buffer cache,说明数据没被缓存或设置 _serial_direct_read=always
- read by other session:热块争用信号,不是 I/O 瓶颈本身,而是并发访问同一数据块的副作用
- 避开 log file sync(属 Commit 类)、enq: TX - row lock contention(属 Application 类),它们虽然也耗时,但根源不在 I/O 子系统
容易被忽略的关联点:I/O 等待和 CURRENT_OBJ# 的关系
ASH 中 current_obj# 仅在会话正访问某个对象(如执行 SELECT 或 UPDATE)时才非空。如果查到某 sql_id 的 wait_class = 'User I/O' 但 current_obj# = 0,大概率是:
- SQL 正在做大量解析(硬解析多时,library cache lock 可能被误归为 I/O 类?不,它属 Concurrency)
- 等待发生在 PL/SQL 执行阶段(如 UDF 调用外部过程)
- 数据来自物化视图刷新或 DBMS_STATS 任务(这类后台操作即使设为前台也会丢对象上下文)
此时应切到 v$sql_plan 查该 sql_id 的执行计划,重点看 operation 是否含 TABLE ACCESS FULL 或 INDEX RANGE SCAN,再结合 io_cell_offload_eligible_bytes 判断是否启用存储层卸载——这才是 I/O 分布的真实底座。


















