前几天跟销售拜访潜在客户时,客户提到他们近期很头疼的问题:他们自己写的一个查询表空间的语句从上周开始跑不出来了,做了很多尝试未果,比较苦恼,毕竟作为DBA不知道库的空间使用情况,客户心里是有点发毛的。
听完客户描述,我也马上回复说这个问题很常见,都在我们日常巡检的范围内,解决起来很简单。一般查询表空间的语句都是用到了DBA_FREE_SPACE这张视图,查询起来慢,常见原因一般如下:


exec dmbs_stats.gather_fixed_objects_stats;
exec dbms_stats.gather_dictionary_stats(degree =>8,cascade =>true);
客户听完介绍的解决方案,表示认同并希望现场帮他们处理好问题,热情地带我去他的工位。虽然库并非生产数据库,但访问这个库依然需要通过堡垒机,而且命令需要手敲。
客户通过shell脚本SQLplus进入数据库后运行语句类似以下SQL:
SELECT UPPER(F.TABLESPACE_NAME) AS "表空间名称",
ROUND(D.AVAILB_BYTES ,2) AS "表空间大小(G)",
ROUND(D.MAX_BYTES,2) AS "最终表空间大小(G)",
ROUND((D.AVAILB_BYTES - F.USED_BYTES),2) AS "已使用空间(G)",
TO_CHAR(ROUND((D.AVAILB_BYTES - F.USED_BYTES) / D.AVAILB_BYTES * 100,
ROUND(F.USED_BYTES, 6) AS "空闲空间(G)",
ROUND(SUM(BYTES) / (1024 * 1024 * 1024), 6) USED_BYTES,
ROUND(MAX(BYTES) / (1024 * 1024 * 1024), 6) MAX_BYTES
ROUND(SUM(DD.BYTES) / (1024 * 1024 * 1024), 6) AVAILB_BYTES,
ROUND(SUM(DECODE(DD.MAXBYTES, 0, DD.BYTES, DD.MAXBYTES))/(1024*1024*1024),6) MAX_BYTES
GROUP BY DD.TABLESPACE_NAME) D
WHERE D.TABLESPACE_NAME = F.TABLESPACE_NAME
客户运行了脚本,果然卡住了一样,无返回结果。演示完,客户让出了座位,示意我可以上机操作了。因为不是生产库,客户也表示可以随意操作,于是没有去进一步确认信息,顺利完成了上面列的操作。
一个客户DBA说,他觉得是IO太慢,因为库不是放在存储上的,而且raid可能用的是raid 5之类。
为了避免问题走偏,我快速打了个快照,做了awr报告,确认了一下单块读约3ms,数据文件个数约800个。证伪了以上假设。
那到底为什么SQL查询还是不快呢?我有点犹豫,毕竟堡垒机命令都手敲不能直接跑自己的脚本包。客户主要负责的DBA此时主动给台阶让我下,让我找时间再看看,回去了再研究,毕竟他们也快下班了。
那么,这2GB的IO来源于哪儿呢?执行计划很长,截取部分如下:
第一部分IO占54%来源WRH$_ACTIVE_SESSION_HISTORY:
第二部分IO占46%来源WRH$_ACTIVE_SESSION_HISTORY:
SELECT partition_name from DBA_tab_partitions
WHERE table_name = 'WRH$_ACTIVE_SESSION_HISTORY';
TYPE partrec IS RECORD (snapid number, dbid number);
TYPE partlist IS TABLE OF partrec;
dbms_output.put_line('PARTITION NAME SNAP_ID DBID');
dbms_output.put_line('--------------------------- ------- ----------');
execute immediate query1 bulk collect into OutList;
for i in OutList.first..OutList.last loop
dbms_output.put_line(part.partition_name||' Min '||OutList(i).snapid||' '||OutList(i).dbid);
execute immediate query2 bulk collect into OutList;
for i in OutList.first..OutList.last loop
dbms_output.put_line(part.partition_name||' Max '||OutList(i).snapid||' '||OutList(i).dbid);
--------------------------- ------- ----------
WRH$_ACTIVE_1489418862_4171 Min 4180 1489418862
WRH$_ACTIVE_1489418862_4171 Max 4181 1489418862
WRH$_ACTIVE_1489418862_4182 Min 4182 1489418862
WRH$_ACTIVE_1489418862_4182 Max 4194 1489418862
WRH$_ACTIVE_1489418862_4195 Min 4195 1489418862
WRH$_ACTIVE_1489418862_4195 Max 4218 1489418862
WRH$_ACTIVE_1489418862_4219 Min 4219 1489418862
WRH$_ACTIVE_1489418862_4219 Max 4242 1489418862
WRH$_ACTIVE_1489418862_4243 Min 4243 1489418862
WRH$_ACTIVE_1489418862_4243 Max 4266 1489418862
WRH$_ACTIVE_1489418862_4267 Min 4267 1489418862
WRH$_ACTIVE_1489418862_4267 Max 4290 1489418862
WRH$_ACTIVE_1489418862_4291 Min 4291 1489418862
WRH$_ACTIVE_1489418862_4291 Max 4314 1489418862
exec DBMS_WORKLOAD_REPOSITORY.DROP_SNAPSHOT_RANGE(low_snap_id =>2810,high_snap_id =>18000);
alter table WRH$_ACTIVE_SESSION_HISTORY shrink space;
alter table sys.wrh$_active_session_history truncate partition WRH$_ACTIVE_xxxx_xxxx update global indexes; alter session set "_swrf_test_action" = 72;本文处理时有点经验主义,这步操作没抱太大期望,结果顺利达到预期效果。也有相当部分场景执行命令后还是不能自动分区,这时候,没错要打补丁了。数据库版本从11.2.0.2到12.1.0.1,可在线打。
