【DB笔试面试637】在Oracle中,直方图使用示例。

  • 2019 年 10 月 10 日
  • 笔记

题目部分

在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程序员面试笔试宝典》,作者:小麦苗