题目部分
在Oracle中,直方图使用示例。
♣
答案部分
下面给出直方图的一个示例,先准备数据分布不均衡的一张表:
CREATE TABLE T_ST_20170604_LHR AS SELECT ROWNUM ID,ROWNUM SAL FROM DUAL CONNECT BY LEVEL<=10000;
UPDATE T_ST_20170604_LHR SET SAL=5000 WHERE SAL BETWEEN 6 AND 9995; --9990
CREATE INDEX T_ST_20170604_LHR_SAL ON T_ST_20170604_LHR(SAL);
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'T_ST_20170604_LHR',CASCADE=>TRUE,METHOD_OPT=>'FOR COLUMNS SAL SIZE 1');
查询数据信息:
SYS@orclasm > SET LINESIZE 9999
SYS@orclasm > COL COLUMN_NAME FORMAT A15
SYS@orclasm > SELECT TABLE_NAME,COLUMN_NAME,ENDPOINT_NUMBER,ENDPOINT_VALUE FROM DBA_TAB_HISTOGRAMS WHERE TABLE_NAME='T_ST_20170604_LHR';
TABLE_NAME COLUMN_NAME ENDPOINT_NUMBER ENDPOINT_VALUE
------------------------------ --------------- --------------- --------------
T_ST_20170604_LHR SAL 0 1
T_ST_20170604_LHR SAL 1 10000
SYS@orclasm > SELECT D.COLUMN_NAME,
2 D.NUM_DISTINCT,
3 D.NUM_NULLS,
4 D.NUM_BUCKETS,
5 D.HISTOGRAM
6 FROM DBA_TAB_COL_STATISTICS D
7 WHERE D.TABLE_NAME = 'T_ST_20170604_LHR';
COLUMN_NAME NUM_DISTINCT NUM_NULLS NUM_BUCKETS HISTOGRAM
--------------- ------------ ---------- ----------- ---------------
SAL 11 0 1 NONE
列SAL上只有最大值,最小值两条记录分别对应端点号(ENDPOINT_NUMBER)0和1,这种显示说明列SAL没有直方图信息,从DBA_TAB_COL_STATISTICS的HISTOGRAM列值为NONE也说明列SAL没有直方图。
SYS@orclasm > SET AUTOT TRACE
SYS@orclasm > SELECT * FROM T_ST_20170604_LHR WHERE SAL=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 738598333
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 909 | 6363 | 4 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_ST_20170604_LHR | 909 | 6363 | 4 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_ST_20170604_LHR_SAL | 909 | | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SAL"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
592 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SYS@orclasm > SELECT * FROM T_ST_20170604_LHR WHERE SAL=5000;
9990 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 738598333
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 909 | 6363 | 4 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_ST_20170604_LHR | 909 | 6363 | 4 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_ST_20170604_LHR_SAL | 909 | | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SAL"=5000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1368 consistent gets
0 physical reads
0 redo size
244008 bytes sent via SQL*Net to client
7835 bytes received via SQL*Net from client
667 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9990 rows processed
SYS@orclasm > SELECT ROUND(10000*(1/11)) FROM DUAL;
ROUND(10000*(1/11))
-------------------
909
SAL列为等值查询,全部选择索引范围扫描,且预估行数(Rows)均为909。Rows的值来源于:ROUND(NUM_ROWS*(1/NUM_DISTINCT_VAL))=ROUND(10000*(1/11))=909,和执行计划里的909相吻合,因为没有收集列的直方图信息,所以优化器估算返回行数和实际返回行数还是有不少差距。
下面针对SAL列收集直方图:
SYS@orclasm > SET AUTOT OFF
SYS@orclasm > EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'T_ST_20170604_LHR',CASCADE=>TRUE,METHOD_OPT=>'FOR COLUMNS SAL SIZE AUTO');
PL/SQL procedure successfully completed.
SYS@orclasm > SELECT D.COLUMN_NAME,D.NUM_DISTINCT,D.NUM_NULLS,D.NUM_BUCKETS,D.HISTOGRAM FROM DBA_TAB_COL_STATISTICS D WHERE D.TABLE_NAME = 'T_ST_20170604_LHR';
COLUMN_NAME NUM_DISTINCT NUM_NULLS NUM_BUCKETS HISTOGRAM
--------------- ------------ ---------- ----------- ---------------
SAL 11 0 11 FREQUENCY
SYS@orclasm > SELECT TABLE_NAME,COLUMN_NAME,ENDPOINT_VALUE,ENDPOINT_NUMBER,NVL((ENDPOINT_NUMBER-(LAG(ENDPOINT_NUMBER) OVER (ORDER BY ENDPOINT_VALUE))),ENDPOINT_NUMBER) COUNTS FROM DBA_TAB_HISTOGRAMS WHERE TABLE_NAME='T_ST_20170604_LHR';
TABLE_NAME COLUMN_NAME ENDPOINT_VALUE ENDPOINT_NUMBER COUNTS
------------------------------ --------------- -------------- --------------- ----------
T_ST_20170604_LHR SAL 1 1 1
T_ST_20170604_LHR SAL 2 2 1
T_ST_20170604_LHR SAL 3 3 1
T_ST_20170604_LHR SAL 4 4 1
T_ST_20170604_LHR SAL 5 5 1
T_ST_20170604_LHR SAL 5000 9995 9990
T_ST_20170604_LHR SAL 9996 9996 1
T_ST_20170604_LHR SAL 9997 9997 1
T_ST_20170604_LHR SAL 9998 9998 1
T_ST_20170604_LHR SAL 9999 9999 1
T_ST_20170604_LHR SAL 10000 10000 1
11 rows selected.
SYS@orclasm > SELECT SAL ENDPOINT_VALUE,SUM(COUNT(*)) OVER(ORDER BY SAL RANGE UNBOUNDED PRECEDING) ENDPOINT_NUMBER,COUNT(*) COUNTS FROM T_ST_20170604_LHR T GROUP BY T.SAL;
ENDPOINT_VALUE ENDPOINT_NUMBER COUNTS
-------------- --------------- ----------
1 1 1
2 2 1
3 3 1
4 4 1
5 5 1
5000 9995 9990
9996 9996 1
9997 9997 1
9998 9998 1
9999 9999 1
10000 10000 1
11 rows selected.
SYS@orclasm >
由于列SAL唯一值的个数没有超过254,所以Oracle自动收集频率直方图。NUM_BUCKETS表示桶数,一共有11个Buckets,所以在DBA_TAB_HISTOGRAMS中,ENDPOINT_VALUE列记录的就是这11个不同的DISTINCT值。ENDPOINT_NUMBER则记录了到此DISTINCT值为止累加的行数。
SYS@orclasm > SET AUTOT TRACE
SYS@orclasm > SELECT * FROM T_ST_20170604_LHR WHERE SAL=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 738598333
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_ST_20170604_LHR | 1 | 7 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_ST_20170604_LHR_SAL | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SAL"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
592 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SYS@orclasm > SELECT * FROM T_ST_20170604_LHR WHERE SAL=5000;
9990 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 513072079
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9990 | 69930 | 7 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T_ST_20170604_LHR | 9990 | 69930 | 7 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SAL"=5000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1368 consistent gets
0 physical reads
0 redo size
244008 bytes sent via SQL*Net to client
7835 bytes received via SQL*Net from client
667 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9990 rows processed
在SAL=1时候使用了索引扫描,而SAL=5000时候,已经使用全表扫描了,说明直方图起了作用。从执行计划的Rows部分也可以看出Oracle计算出来的Cardinality是9990,和实际的情况完全吻合(这里的Rows为CURRENT_ENDPOINT_NUMBER-PREVIOUS_ENDPOINT_NUMBER)。可以看出这种频率直方图统计的列的信息是非常精确的。
下面重新对列SAL收集直方图,让Bucket的数量为9,小于SAL列的DISTINCT的值,那么Oracle会收集高度直方图。
SYS@orclasm > SET AUTOT OFF
SYS@orclasm > EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'T_ST_20170604_LHR',CASCADE=>TRUE,METHOD_OPT=>'FOR COLUMNS SAL SIZE 9');
PL/SQL procedure successfully completed.
SYS@orclasm > SELECT D.COLUMN_NAME,D.NUM_DISTINCT,D.NUM_NULLS,D.NUM_BUCKETS,D.HISTOGRAM FROM DBA_TAB_COL_STATISTICS D WHERE D.TABLE_NAME = 'T_ST_20170604_LHR';
COLUMN_NAME NUM_DISTINCT NUM_NULLS NUM_BUCKETS HISTOGRAM
--------------- ------------ ---------- ----------- ---------------
SAL 11 0 9 HEIGHT BALANCED
SYS@orclasm > SELECT TABLE_NAME,COLUMN_NAME,ENDPOINT_VALUE,ENDPOINT_NUMBER FROM DBA_TAB_HISTOGRAMS WHERE TABLE_NAME='T_ST_20170604_LHR';
TABLE_NAME COLUMN_NAME ENDPOINT_VALUE ENDPOINT_NUMBER
------------------------------ --------------- -------------- ---------------
T_ST_20170604_LHR SAL 1 0
T_ST_20170604_LHR SAL 5000 8
T_ST_20170604_LHR SAL 10000 9
在高度平衡直方图中,在DBA_TAB_HISTOGRAMS视图中,EDNPOINT_NUMBER代表桶号,且自动省去EDNPOINT_VALUE值相同且ENDPOINT_NUMBER相邻的桶的值(节省空间,合并存储)。ENDPOINT_VALUE表示每一个桶中的最大值,而第一个桶记录的是最小值(Bucket为0的行,即EDNPOINT_NUMBER为0的行)。重复出现为ENDPOINT_VALUE的值称为Popular Value,这里的0即Popular Value。显然,Popular Value所在记录的ENDPOINT_NUMBER值和它上一条记录的ENDPOINT_NUMBER值之间的差值越大,则意味着该Popular Value在目标表中所占的比例也就越大,它所对应的Cardinality也就越大。
一共有9个桶(不包含0号Bucket),在该直方图图中,1到7号桶被省略存储,说明和8号桶的存储是一样的。
SYS@orclasm > SET AUTOT TRACE
SYS@orclasm > SELECT * FROM T_ST_20170604_LHR WHERE SAL=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 738598333
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 111 | 777 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_ST_20170604_LHR | 111 | 777 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_ST_20170604_LHR_SAL | 111 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SAL"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
592 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SYS@orclasm > SELECT * FROM T_ST_20170604_LHR WHERE SAL=5000;
9990 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 513072079
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8889 | 62223 | 7 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| T_ST_20170604_LHR | 8889 | 62223 | 7 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SAL"=5000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1368 consistent gets
0 physical reads
0 redo size
244008 bytes sent via SQL*Net to client
7835 bytes received via SQL*Net from client
667 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9990 rows processed
SYS@orclasm >
在高度平衡直方图中执行计划的列的选择性就不是那么的精确了,而在现实很多时候,列的唯一值是超过254的,那么只能使用高度平衡直方图了。
如果需要删除直方图信息,在Oracle 10g中可以通过设置“METHOD_OPT=>'FOR COLUMNS SAL SIZE 1'”,但这却得再次收集表的统计信息,十分不合理,所以,在Oracle 11g中,有如下方法可以直接删除直方图信息:
EXEC DBMS_STATS.DELETE_COLUMN_STATS(USER,'T_ST_20170604_LHR','SAL',COL_STAT_TYPE => 'HISTOGRAM');
其中,COL_STAT_TYPE默认为ALL,表示删除列的基本统计信息和直方图信息。
本文选自《Oracle程序员面试笔试宝典》,作者:小麦苗