查询DBA_FREE_SPACE特别慢

查询慢的现象

在查询数据文件的剩余空间或者查询表空间使用率时,可能会碰见查询很慢的情况,主要原因是在于查询dba_free_space视图的效率特别低。

1
2
3
4
5
6
7
8
SQL> set timing on
SQL> select /*+ gather_plan_statistics */count(1) from dba_free_space;

COUNT(1)
----------
12493

已用时间: 00: 00: 03.54

查询一个12493行数据的视图花了大概4s,执行计划如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
select /*+ gather_plan_statistics */count(1) from dba_free_space

Plan hash value: 1995021683

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | | 41 (100)| 1 |00:00:03.54 | 682K| 166K| | | |
| 1 | SORT AGGREGATE | | 1 | 1 | | | 1 |00:00:03.54 | 682K| 166K| | | |
| 2 | VIEW | DBA_FREE_SPACE | 1 | 15 | | 41 (44)| 12493 |00:00:00.04 | 682K| 166K| | | |
| 3 | UNION-ALL | | 1 | | | | 12493 |00:00:00.04 | 682K| 166K| | | |
| 4 | NESTED LOOPS | | 1 | 1 | 40 | 5 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 5 | NESTED LOOPS | | 1 | 1 | 33 | 4 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 6 | TABLE ACCESS CLUSTER | FET$ | 1 | 1 | 26 | 3 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 7 | INDEX FULL SCAN | I_TS# | 1 | 1 | | 1 (0)| 32 |00:00:00.01 | 1 | 0 | | | |
|* 8 | TABLE ACCESS CLUSTER | TS$ | 0 | 1 | 7 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 9 | INDEX UNIQUE SCAN | I_TS# | 0 | 1 | | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 10 | INDEX UNIQUE SCAN | I_FILE2 | 0 | 1 | 7 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 11 | HASH JOIN | | 1 | 3 | 99 | 2 (0)| 5184 |00:00:00.04 | 952 | 0 | 2716K| 2716K| 1803K (0)|
| 12 | NESTED LOOPS | | 1 | 3 | 45 | 1 (0)| 5184 |00:00:00.04 | 919 | 0 | | | |
|* 13 | FIXED TABLE FULL | X$KTFBFE | 1 | 3 | 24 | 0 (0)| 5184 |00:00:00.03 | 915 | 0 | | | |
|* 14 | INDEX UNIQUE SCAN | I_FILE2 | 5184 | 1 | 7 | 1 (0)| 5184 |00:00:00.01 | 4 | 0 | | | |
|* 15 | TABLE ACCESS CLUSTER | TS$ | 1 | 1 | 18 | 1 (0)| 30 |00:00:00.01 | 33 | 0 | | | |
| 16 | INDEX FULL SCAN | I_TS# | 1 | 1 | | 1 (0)| 32 |00:00:00.01 | 1 | 0 | | | |
|* 17 | HASH JOIN | | 1 | 9 | 909 | 23 (79)| 7309 |00:00:03.50 | 681K| 166K| 2546K| 2546K| 1793K (0)|
|* 18 | HASH JOIN | | 1 | 10 | 830 | 22 (82)| 7309 |00:00:00.04 | 681K| 166K| 1572K| 1572K| 1567K (0)|
| 19 | MERGE JOIN | | 1 | 605 | 10890 | 5 (20)| 597 |00:00:00.01 | 38 | 0 | | | |
| 20 | TABLE ACCESS BY INDEX ROWID| RECYCLEBIN$ | 1 | 582 | 6402 | 3 (0)| 574 |00:00:00.01 | 37 | 0 | | | |
| 21 | INDEX FULL SCAN | RECYCLEBIN$_TS | 1 | 582 | | 1 (0)| 574 |00:00:00.01 | 11 | 0 | | | |
|* 22 | SORT JOIN | | 574 | 63 | 441 | 2 (50)| 597 |00:00:00.01 | 1 | 0 | 9216 | 9216 | 8192 (0)|
| 23 | INDEX FULL SCAN | I_FILE2 | 1 | 63 | 441 | 1 (0)| 65 |00:00:00.01 | 1 | 0 | | | |
|* 24 | FIXED TABLE FULL | X$KTFBUE | 1 | 1000 | 65000 | 17 (100)| 584K|00:00:03.35 | 681K| 166K| | | |
|* 25 | TABLE ACCESS CLUSTER | TS$ | 1 | 1 | 18 | 1 (0)| 30 |00:00:00.01 | 33 | 0 | | | |
| 26 | INDEX FULL SCAN | I_TS# | 1 | 1 | | 1 (0)| 32 |00:00:00.01 | 1 | 0 | | | |
| 27 | NESTED LOOPS | | 1 | 1 | 64 | 6 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 28 | NESTED LOOPS | | 1 | 83 | 64 | 6 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 29 | NESTED LOOPS | | 1 | 1 | 53 | 5 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 30 | NESTED LOOPS | | 1 | 1 | 46 | 4 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
|* 31 | TABLE ACCESS CLUSTER | TS$ | 1 | 1 | 7 | 3 (0)| 0 |00:00:00.01 | 33 | 0 | | | |
| 32 | INDEX FULL SCAN | I_TS# | 1 | 1 | | 1 (0)| 32 |00:00:00.01 | 1 | 0 | | | |
| 33 | TABLE ACCESS CLUSTER | UET$ | 0 | 1 | 39 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 34 | INDEX RANGE SCAN | I_FILE#_BLOCK# | 0 | 1 | | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 35 | INDEX UNIQUE SCAN | I_FILE2 | 0 | 1 | 7 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 36 | INDEX RANGE SCAN | RECYCLEBIN$_TS | 0 | 83 | | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 37 | TABLE ACCESS BY INDEX ROWID | RECYCLEBIN$ | 0 | 1 | 11 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
| 38 | NESTED LOOPS | | 1 | 1 | 34 | 4 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
| 39 | NESTED LOOPS | | 1 | 1 | 31 | 3 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
| 40 | TABLE ACCESS FULL | NEW_LOST_WRITE_EXTENTS$ | 1 | 1 | 13 | 2 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 41 | TABLE ACCESS CLUSTER | TS$ | 0 | 1 | 18 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 42 | INDEX UNIQUE SCAN | I_TS# | 0 | 1 | | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
|* 43 | INDEX RANGE SCAN | I_FILE2 | 0 | 1 | 3 | 1 (0)| 0 |00:00:00.01 | 0 | 0 | | | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

8 - filter("TS"."BITMAPPED"=0)
9 - access("TS"."TS#"="F"."TS#")
10 - access("F"."TS#"="FI"."TS#" AND "F"."FILE#"="FI"."RELFILE#")
11 - access("TS"."TS#"="KTFBFETSN")
13 - filter(("CON_ID"=0 OR "CON_ID"=3))
14 - access("KTFBFETSN"="FI"."TS#" AND "KTFBFEFNO"="FI"."RELFILE#")
15 - filter(("TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$") AND BITAND("TS"."FLAGS",4503599627370496)<>4503599627370496))
17 - access("TS"."TS#"="RB"."TS#")
18 - access("KTFBUEFNO"="FI"."RELFILE#" AND "KTFBUESEGTSN"="RB"."TS#" AND "KTFBUESEGFNO"="RB"."FILE#" AND "KTFBUESEGBNO"="RB"."BLOCK#")
22 - access("RB"."TS#"="FI"."TS#")
filter("RB"."TS#"="FI"."TS#")
24 - filter(("CON_ID"=0 OR "CON_ID"=3))
25 - filter(("TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$") AND BITAND("TS"."FLAGS",4503599627370496)<>4503599627370496))
31 - filter("TS"."BITMAPPED"=0)
34 - access("TS"."TS#"="U"."TS#")
35 - access("U"."TS#"="FI"."TS#" AND "U"."SEGFILE#"="FI"."RELFILE#")
36 - access("U"."TS#"="RB"."TS#")
37 - filter(("U"."SEGBLOCK#"="RB"."BLOCK#" AND "U"."SEGFILE#"="RB"."FILE#"))
41 - filter((BITAND("TS"."FLAGS",4503599627370496)=4503599627370496 AND "TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$")))
42 - access("TS"."TS#"="F"."EXTENT_DATAFILE_TSID")
43 - access("F"."EXTENT_DATAFILE_TSID"="FI"."TS#")

- this is an adaptive plan

从执行计划可以看到消耗性能的重点在于第18-24行过程中,通过先对RECYCLEBIN$索引全扫描,在对索引I_FILE2(FILE$表)全扫描,merge后得到的结果与X$KTFBUE表进行hash join,而这里X$KTFBUE估算为1000行,实际为584K行。由于对X$KTFBUE的全表扫描,表示Oracle要读取数据库里每一个segment的header块(这里并不清楚这估算的1000行数据怎么来的,这个X$KTFBUE表上并没有统计信息)

1
2
3
4
5
6
7
8
9
10
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|* 18 | HASH JOIN | | 1 | 10 | 830 | 22 (82)| 7309 |00:00:00.04 | 681K| 166K| 1572K| 1572K| 1567K (0)|
| 19 | MERGE JOIN | | 1 | 605 | 10890 | 5 (20)| 597 |00:00:00.01 | 38 | 0 | | | |
| 20 | TABLE ACCESS BY INDEX ROWID| RECYCLEBIN$ | 1 | 582 | 6402 | 3 (0)| 574 |00:00:00.01 | 37 | 0 | | | |
| 21 | INDEX FULL SCAN | RECYCLEBIN$_TS | 1 | 582 | | 1 (0)| 574 |00:00:00.01 | 11 | 0 | | | |
|* 22 | SORT JOIN | | 574 | 63 | 441 | 2 (50)| 597 |00:00:00.01 | 1 | 0 | 9216 | 9216 | 8192 (0)|
| 23 | INDEX FULL SCAN | I_FILE2 | 1 | 63 | 441 | 1 (0)| 65 |00:00:00.01 | 1 | 0 | | | |
|* 24 | FIXED TABLE FULL | X$KTFBUE | 1 | 1000 | 65000 | 17 (100)| 584K|00:00:03.35 | 681K| 166K| | | |

对查询的过程做trace,发现大部分的时间都在等待db file sequential read事件,也就是要读取每个segment的header块。

1
2
3
4
5
6
7
8
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SID, USERNAME , TYPE, STATISTIC , DELTA, HDELTA/SEC, %TIME, GRAPH , NUM_WAITS, WAITS/SEC, AVERAGES
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
279, SYS , WAIT, latch: cache buffers chains , 3525, 174.95us, .0%, [ ], 405, 20.1, 8.7us average wait
279, SYS , WAIT, read by other session , 3052684, 151.51ms, 15.2%, [WW ], 3530, 175.2, 864.78us average wait
279, SYS , WAIT, db file sequential read , 16622726, 825ms, 82.5%, [WWWWWWWWW ], 13254, 657.81, 1.25ms average wait

-- End of Stats snap 1, end=2023-04-04 10:25:32, seconds=20.1

原因分析

  • 如果回收站没有数据的话,那么这个hash join和table full也就不会发生,然而在这个环境当中recyclebin是启用的,里面有574行记录,所以导致全表扫描的发生。
  • 第二点, 即使回收站里面有数据,如果访问X$KTFBUE不是走全表扫描,而是走索引的话那么效率也会高的多,代价也没有这么高。

所以从两个方向来进行思考:

  1. 为什么查询剩余空间会查询到recyclebin$表,回收站里也会占据空间吗?那么是算可用还是不可用?
  2. 为什么X$KTFBUE的估算行数跟实际行数差别这么大?为什么没有统计信息?要如何收集它的统计信息呢?

Recyclebin的空间占用

表空间TS_XB为我用来测试的例子,通过下面这个sql可以得出表空间TS_XB的空间大小、剩余空间、已分配到extents的空间、已分配到segments的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select 'File space' What, nvl(sum(user_bytes)/1048576,0) MB from dba_data_files where tablespace_name = 'TS_XB'
union all
select 'Free space', nvl(sum(bytes/1048576),0) from dba_free_space where tablespace_name = 'TS_XB'
union all
select 'Extents', nvl(sum(bytes/1048576),0) from dba_extents where tablespace_name = 'TS_XB'
union all
select 'Segments', nvl(sum(bytes/1048576),0) from dba_segments where tablespace_name = 'TS_XB'
;

WHAT MB
---------- ----------
File space 38
Free space 38
Extents 0
Segments 34

这里能观察到,File spaceFree space大小基本是一致的,而分配到segments的空间大小则有34M,分配到extents的空间则为0。通过这几个数据你觉得这个表空间到底是空闲还是已满呢?

这里既然segments已经分配了34MB的大小,可以先查询到底是那些segments占用了

1
2
3
4
5
6
SQL> select segment_name from dba_segments where tablespace_name='TS_XB';

SEGMENT_NAME
--------------------------------------------------------------------------------
BIN$+GjLhdmVcGbgVXcViOqQBQ==$0
BIN$+GjLhdmUcGbgVXcViOqQBQ==$0

可以看到几乎都是BIN$开头的对象,也就是处于回收站当中的对象。即使被drop掉以后仍然占据了segments空间,但是Oracle又认为这部分占用空间是可以回收利用的,所以也将其算在了dba_free_space其中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SQL> purge recyclebin;

Recyclebin purged.

SQL> select 'File space' What, nvl(sum(user_bytes)/1048576,0) MB from dba_data_files where tablespace_name = 'TS_XB'
2 union all
3 select 'Free space', nvl(sum(bytes/1048576),0) from dba_free_space where tablespace_name = 'TS_XB'
4 union all
5 select 'Extents', nvl(sum(bytes/1048576),0) from dba_extents where tablespace_name = 'TS_XB'
6 union all
7 select 'Segments', nvl(sum(bytes/1048576),0) from dba_segments where tablespace_name = 'TS_XB'
8 ;

WHAT MB
---------- ----------
File space 38
Free space 38
Extents 0
Segments 0

清理完回收站后,可以看到segments的空间也被立即释放出来了。

收集Fixed Table STATS

现在的表是没有统计信息的,默认情况下就是1000行,那么很容易联想到导致全表扫描的原因是因为没有统计信息。

1
2
3
4
5
SQL> select owner,table_name,NUM_ROWS,BLOCKS,LAST_ANALYZED from dba_tab_statistics where  table_name='X$KTFBUE';

OWNER TABLE_NAME NUM_ROWS BLOCKS LAST_ANALYZED
------------------------------ ------------------------------ ---------- ---------- -------------------
SYS X$KTFBUE

通常对于fixed table收集统计信息都是通过存储过程DBMS_STATS.GATHER_FIXED_OBJECTS_STATS,但是这个也是要对所有fixed表进行收集,也会对性能有影响。

1
exec DBMS_STATS.GATHER_FIXED_OBJECTS_STATS;

然而你会发现,即使这样收集完以后,X$KTFBUE表上仍然是没有任何统计信息。

这是由于Oracle认为收集这些fixed表的统计信息:

  • 会带来较大的性能消耗
  • 不收集SQL性能会更好
  • 这些表不支持收集统计信息
  • 统计信息过期的太快

X$KTFBUE表就属于上面这些因素的其中一张表,这种表不止一个,还有很多其他的fixed表也是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select last_analyzed, count(1)
from (SELECT case
when last_analyzed is null then
'无统计信息'
else
'包含统计信息'
end last_analyzed
FROM DBA_TAB_STATISTICS
WHERE OBJECT_TYPE = 'FIXED TABLE')
group by last_analyzed;

LAST_ANALYZE COUNT(1)
------------ ----------
无统计信息 157
包含统计信息 1178

直接对表进行统计信息收集

1
2
3
4
5
6
7
exec DBMS_STATS.GATHER_TABLE_STATS('SYS','X$KTFBUE');

SQL> select owner,table_name,NUM_ROWS,BLOCKS,LAST_ANALYZED from dba_tab_statistics where table_name='X$KTFBUE';

OWNER TABLE_NAME NUM_ROWS BLOCKS LAST_ANALYZED
------------------------------ -------------------- ---------- ---------- -------------------
SYS X$KTFBUE 586359 2023-04-04 13:51:40

结果验证

经过清空回收站和收集统计信息以后,重新查询DBA_FREE_SPACE

1
2
3
4
5
6
7
SQL> select /*+ gather_plan_statistics */count(1) from dba_free_space;

COUNT(1)
----------
6636

已用时间: 00: 00: 00.03

执行计划由于回收站的数据为空,实际也就没有产生对X$KTFBUE的全表扫描

1
2
3
4
5
6
7
8
9
10
11
12
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|* 17 | HASH JOIN | | 1 | 9 | 909 | 23 (79)| 0 |00:00:00.01 | 11 | 2546K| 2546K| 522K (0)|
|* 18 | HASH JOIN | | 1 | 10 | 830 | 22 (82)| 0 |00:00:00.01 | 11 | 1572K| 1572K| 480K (0)|
| 19 | MERGE JOIN | | 1 | 605 | 10890 | 5 (20)| 0 |00:00:00.01 | 11 | | | |
| 20 | TABLE ACCESS BY INDEX ROWID| RECYCLEBIN$ | 1 | 582 | 6402 | 3 (0)| 0 |00:00:00.01 | 11 | | | |
| 21 | INDEX FULL SCAN | RECYCLEBIN$_TS | 1 | 582 | | 1 (0)| 0 |00:00:00.01 | 11 | | | |
|* 22 | SORT JOIN | | 0 | 63 | 441 | 2 (50)| 0 |00:00:00.01 | 0 | 9216 | 9216 | 8192 (0)|
| 23 | INDEX FULL SCAN | I_FILE2 | 0 | 63 | 441 | 1 (0)| 0 |00:00:00.01 | 0 | | | |
|* 24 | FIXED TABLE FULL | X$KTFBUE | 0 | 1000 | 65000 | 17 (100)| 0 |00:00:00.01 | 0 | | | |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

BTW

  • X$KTFBUE表到底是干什么用的?

是根据LMT(locally managed tablespace)中使用的extents信息动态生成的表。可以看到X$KTFBUE的数据量几乎跟数据库里的extents数量接近

1
2
3
4
5
6
7
8
9
10
11
SQL> select count(1) from dba_extents;

COUNT(1)
----------
580315

SQL> select count(1) from X$KTFBUE;

COUNT(1)
----------
580328
  • X$KTFBUE表中字段代表什么意思
1
2
3
4
5
6
7
8
9
10
11
12
13
ADDR : N/A
INDX : N/A
INST_ID : N/A
KTFBUESEGTSN : ts# containing this segment
KTFBUESEGFNO : Relative number of the file containing the segment header
KTFBUESEGBNO : segment header block number
KTFBUEEXTNO : Extent number
KTFBUEFNO : Relative number of the file containing the extent
KTFBUEBNO : Starting block number of the extent
KTFBUEBLKS : Size of the extent in ORACLE blocks
KTFBUECTM : commit_jtime,Commit Time of the undo in the extent expressed as Julian date
KTFBUESTT :commit_wtime,Commit Time of the undo in the extent expressed as wall clock time
KTFBUESTA : Transaction Status of the undo in the extent;1, 'ACTIVE', 2, 'EXPIRED', 3, 'UNEXPIRED', 0 for non‐undo

查询DBA_FREE_SPACE特别慢
https://www.xbdba.com/2023/04/04/query-slow-on-dba-free-space/
作者
xbdba
发布于
2023年4月4日
许可协议