上周五凌晨2点37分,生产环境监控系统突然发出刺耳的警报声。某核心业务系统的Oracle数据库响应时间从平均200ms飙升到12秒,前端应用出现大面积超时。登录服务器查看AWR报告,发现"disk sorts"指标异常增高,临时表空间使用率达到98%。这种典型的磁盘排序问题如果不及时处理,轻则导致业务卡顿,重则引发整个数据库挂起。
磁盘排序(Disk Sorts)本质上是当内存排序区(sort_area_size或pga_aggregate_target)不足时,Oracle被迫将排序操作转移到磁盘临时表空间进行。与内存排序相比,磁盘I/O速度要慢几个数量级。根据我的经验,当disk sorts/s超过10次时就需要立即介入调查,而当时这个数值已经突破150次/秒。
首先通过OEM(Oracle Enterprise Manager)的实时SQL监控界面,筛选出执行时间最长的TOP 20 SQL语句。重点关注以下字段:
同时打开另一个终端窗口,用以下脚本实时监控排序情况:
sql复制SELECT se.sid, se.serial#, se.username, se.sql_id,
su.extents, su.blocks*8/1024 MB,
su.tablespace, su.segtype
FROM v$session se, v$sort_usage su
WHERE se.saddr = su.session_addr
ORDER BY su.blocks DESC;
在分析AWR报告时,这几个section需要特别关注:
有个容易忽略的细节:如果发现"PGA Cache Hit %"低于85%,说明PGA内存严重不足。但要注意这个指标是累计值,要结合ASH报告看瞬时峰值。
最常见的情况是PGA_AGGREGATE_TARGET设置不合理。通过以下查询检查当前配置:
sql复制SELECT name, value/1024/1024 "Size(MB)"
FROM v$parameter
WHERE name IN ('pga_aggregate_target','sort_area_size');
如果服务器总内存为64GB,OLTP系统的PGA通常建议配置为总内存的20%-25%。但要注意:
某次排查发现一个报表查询导致大量磁盘排序:
sql复制SELECT * FROM orders o, items i
WHERE o.order_id = i.order_id
ORDER BY o.create_time DESC, i.price ASC;
优化方案:
sql复制SELECT /*+ LEADING(o) USE_NL(i) */ *
FROM orders o, items i
WHERE o.order_id = i.order_id
ORDER BY o.create_time DESC, i.price ASC;
遇到过一个案例:临时表空间数据文件初始大小只有100M,但autoextend每次只增长10M。当突发大批量排序时,频繁扩展导致I/O争用。解决方案:
sql复制ALTER TABLESPACE temp ADD TEMPFILE '/oradata/temp02.dbf'
SIZE 2G AUTOEXTEND ON NEXT 1G MAXSIZE 30G;
最佳实践建议:
Oracle提供了多种排序优化技术:
可以通过以下命令检查排序算法:
sql复制ALTER SESSION SET tracefile_identifier='sort_trace';
ALTER SESSION SET events '10032 trace name context forever, level 10';
-- 执行问题SQL
ALTER SESSION SET events '10032 trace name context off';
建议创建定期执行的监控脚本:
sql复制BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'monitor_disk_sorts',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN
INSERT INTO sort_monitor
SELECT SYSDATE, value
FROM v$sysstat
WHERE name = ''sorts (disk)'';
END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=HOURLY',
enabled => TRUE);
END;
/
-- 配套创建趋势分析视图
CREATE VIEW sort_trend AS
SELECT TRUNC(sample_time, 'HH24') hour,
MAX(value) - MIN(value) disk_sorts_per_hour
FROM sort_monitor
GROUP BY TRUNC(sample_time, 'HH24');
曾处理过一个诡异案例:看似简单的查询却产生大量磁盘排序。最终发现是字段类型隐式转换:
sql复制-- account_no是VARCHAR2类型,但传入的是数值
SELECT * FROM accounts
WHERE account_no = 1234567
ORDER BY account_name;
解决方案:
某数据仓库系统启用并行查询后,disk sorts不降反升。这是因为:
调整策略:
sql复制ALTER SESSION SET parallel_degree_policy = 'MANUAL';
ALTER SESSION SET parallel_degree_limit = 4;
-- 对于排序密集型SQL禁用并行
SELECT /*+ NO_PARALLEL */ ...
经过多年处理磁盘排序问题的经验,我总结出这个排查流程图:
关键提示:永远不要在生产环境直接调整排序参数!先在测试库验证效果,使用DBMS_SPM加载最优执行计划,再通过SQL Patch逐步上线。
最后分享一个诊断脚本,可快速生成排序问题分析报告:
sql复制SET LINES 200 PAGES 1000
COL sql_text FOR a60 TRUNC
SELECT s.sql_id, s.disk_sorts, s.sorts_memory,
s.executions, s.disk_sorts/s.executions disk_per_exec,
SUBSTR(t.sql_text,1,60) sql_text
FROM v$sqlarea s, v$sqltext t
WHERE s.sql_id = t.sql_id
AND s.disk_sorts > 100
ORDER BY s.disk_sorts DESC;