直接查v$active_session_history中高频出现db file scattered read的前台会话,需聚合统计最近5分钟内≥10次的采样,并通过program、current_obj#关联dba_objects确认是否扫描大表,再结合执行计划、谓词和统计信息判断是否恶意。

怎么从ASH快速锁定正在扫大表的会话
直接查 v$active_session_history 中高频出现 db file scattered read 的会话,但必须聚合统计、限定时间——单条记录没意义,恶意全表扫描至少持续数秒,会跨多个采样点。
执行以下语句,聚焦最近 5 分钟内该等待 ≥ 10 次的前台会话:
SELECT session_id, session_serial#, username, program, COUNT(*) cnt FROM v$active_session_history WHERE sample_time > SYSDATE - 5/1440 AND event = 'db file scattered read' AND session_type = 'FOREGROUND' GROUP BY session_id, session_serial#, username, program HAVING COUNT(*) >= 10 ORDER BY cnt DESC;
-
program字段很关键:比如jdbc thin client或异常脚本名(如load_data.sh),比username更早暴露来源 - 若
username为空但program可疑,要立刻查machine和terminal追溯客户端 IP - 别漏掉
session_type = 'FOREGROUND'—— 否则后台归档、RMAN 备份等合法 I/O 会淹没真实问题
怎么确认是哪张表被扫、是不是真大表
拿到高频率会话 ID 后,用 current_obj# 关联 dba_objects 定位对象,注意它指向的是被访问的表(不是索引),且只在物理读发生时有值。
SELECT DISTINCT o.object_name, o.object_type, o.owner,
s.blocks * 8 / 1024 AS mb_size
FROM v$active_session_history ash
JOIN dba_objects o ON ash.current_obj# = o.object_id
JOIN dba_segments s ON o.owner = s.owner AND o.object_name = s.segment_name
WHERE ash.session_id = &sid
AND ash.sample_time > SYSDATE - 2/1440
AND ash.event = 'db file scattered read'
AND o.object_type = 'TABLE';
- 如果
mb_size明显大于_small_table_threshold(通常为 buffer cache 的 ~2%),基本坐实是大表全扫 -
current_obj#为空?说明可能是 PL/SQL 匿名块、硬解析失败,或用了绑定变量但未缓存——此时重点盯program+machine - 别信
v$sort_usage.sql_id:它和当前扫描完全无关,ASH 才是唯一可信来源
怎么区分“合理扫”和“恶意扫”
不是所有 db file scattered read 都要 kill。得结合执行计划、业务上下文和 I/O 强度综合判断。
- 看执行频次:
COUNT(*)超过 30 次/5 分钟,且持续多轮,大概率是失控脚本或未加 WHERE 的误操作 - 看对象敏感性:如果是核心账务表、客户主表,哪怕只扫一次也需立即干预;若是日志归档表、ETL 临时表,可能属正常流程
- 看是否伴随其他等待:若同时高频出现
direct path write temp,说明还在排序或哈希连接,I/O 压力会翻倍 - 排除已知任务:检查该时段是否有 scheduled job、数据同步任务、报表导出在运行
为什么查到 SQL_ID 后不能直接改 SQL
找到 sql_id 只是起点,直接重写或加索引常无效,因为根本原因往往藏在统计信息或谓词写法里。
- 先用
DBMS_XPLAN.DISPLAY_AWR看历史执行计划,确认是否真为TABLE ACCESS FULL,且Rows列预估远小于实际(比如估 100 行,实际返回 50 万)→ 很可能是NUM_ROWS陈旧 - 检查谓词:有没有
WHERE to_char(create_time) = '2026-08'这类隐式转换?它会让索引失效,强制全扫 - 分区表注意
PSTART/PSTOP:若显示KEY或1 TO MAX,说明分区裁剪失效,实际扫了全部分区 - 别忽略
parallel_force_local = TRUE:即使写了/*+ parallel(t 8) */,也会被锁死在单节点,导致 I/O 更集中
真正卡住优化器的,往往不是 SQL 文本本身,而是统计不准、类型不匹配、分区键被函数包裹这些细节——它们不会报错,但会让全表扫描成为唯一选择。











