【認知篇】Oracle的直方圖是個嘛,能幹啥?

【引言】
在Oracle中直方圖是一種對數據分佈質量情況進行描述的工具。它會按照某一列不同值出現數量多少,以及出現的頻率高低來繪製數據的分佈情況,以便能夠指導優化器根據數據的分佈做出正確的選擇。在某些情況下,表的列中的數值分佈將會影響優化器使用索引還是執行全表掃描的決策。

構造直方圖最主要的原因:是幫助優化器在表中數據嚴重偏斜時做出更好的規劃

直方圖是一種統計學上的工具,並非Oracle專有,通常情況下它會表現爲一種幾何圖形表,這個圖形表是根據從實際環境中所收集來的被管理對象某個方面的質量分佈情況的數據所繪製成的,通常會畫成以數量爲底邊,以頻度爲高度的一系列連接起來的矩形圖,因此直方圖在統計學上也稱爲質量分佈圖。

當where 子句的值具有不成比例數量的數值時,將出現這種情況,使得全表掃描比索引訪問的成本更低。這種情況下如果where 子句的過濾謂詞列之上有一個合理的正確的直方圖,將會對優化器做出正確的選擇發揮巨大的作用,使得SQL語句執行成本最低從而提升性能。

在分析表或索引時,直方圖用於記錄數據的分佈。通過獲得該信息,基於成本的優化器就可以決定使用將返回少量行的索引,而避免使用基於限制條件返回許多行的索引。直方圖的使用不受索引的限制,可以在表的任何列上構建直方圖。

構造直方圖最主要的原因就是幫助優化器在表中數據嚴重偏斜時做出更好的規劃 。如果一到兩個值構成了表中的大部分數據(數據偏斜),相關的索引就可能無法幫助減少滿足查詢所需的I/O數量。創建直方圖可以讓基於成本的優化器知道何時使用索引才最合適,或何時應該根據WHERE子句中的值返回表中80%的記錄。

通常情況下在以下場合中建議使用直方圖:
1、當Where子句引用了列值分佈存在明顯偏差的列時
當這種偏差相當明顯時,以至於WHERE 子句中的值將會使優化器選擇不同的執行計劃。這時應該使用直方圖來幫助優化器來修正執行路徑。
注意:如果查詢不引用該列,則創建直方圖沒有意義。這種錯誤很常見,許多 DBA 會在偏差列上創建柱狀圖,即使沒有任何查詢引用該列。

2、當列值導致不正確的判斷時
這種情況通常會發生在多表連接時,例如,假設我們有一個五項的表聯接,其結果集只有10行。Oracle 將會以一種使第一個聯接的結果集(集合基數)儘可能小的方式將表聯接起來。通過在中間結果集中攜帶更少的負載,查詢將會運行得更快。爲了使中間結果最小化,優化器嘗試在SQL執行的分析階段評估每個結果集的集合基數。在偏差的列上擁有直方圖將會極大地幫助優化器作出正確的決策。如優化器對中間結果集的大小作出不正確的判斷,它可能會選擇一種未達到最優化的表聯接方法。因此向該列添加直方圖經常會向優化器提供使用最佳聯接方法所需的信息。
在這裏插入圖片描述
等頻直方圖與等高直方圖
默認的,如果一個傾斜列上的唯一值超過了254個,那麼ORACLE會對此列建立等高直方圖,否則建立等頻直方圖。

先來看下等頻直方圖。
所謂的等頻即按照列上的不同數據值進行劃分,由於每個數值的頻度相同,高度不同,故稱爲等頻。下面是具體例子:
建立表TAB,更新字段B,讓列B產生傾斜。並在B列上創建索引。

SQL> create table tab(a number,b number);
Table created.
SQL> insert into tab select rownum,rownum from dual connect by level <=10000;
10000 rows created.
SQL> commit;
Commit complete.
SQL> update tab set b=5 where b between 6 and 9995;
9990 rows updated.
SQL> commit;
Commit complete.
SQL> create index tab_b_idx on tab(b);
Index created.

然後分析表,強制使列B不產生直方圖。

SQL> exec dbms_stats.gather_table_stats('HR','TAB',cascade=>true,
method_opt=>'FOR COLUMNS B SIZE 1');
PL/SQL procedure successfully completed.

method_opt參數的官方解釋如下

method_opt	Accepts either of the following options, or both in combination:
FOR ALL [INDEXED | HIDDEN] COLUMNS [size_clause]
FOR COLUMNS [size clause] column [size_clause] [,column [size_clause]...]
size_clause is defined as size_clause := SIZE {integer | REPEAT | AUTO | SKEWONLY}
column is defined as column := column_name | extension name | extension
  • integer : Number of histogram buckets. Must be in the range [1,254].
  • REPEAT : Collects histograms only on the columns that already have histograms
  • AUTO : Oracle determines the columns to collect histograms based on data distribution and the workload of the columns.
  • SKEWONLY : Oracle determines the columns to collect histograms based on the data distribution of the columns.
  • column_name : Name of a column
  • extension : can be either a column group in the format of (column_name, Colume_name [, …]) or an expression

查看視圖USER_TAB_HISTOGRAMS/USER_HISTOGRAMS 或者DBA_TAB_COL_STATISTICS

SQL> select TABLE_NAME,COLUMN_NAME,ENDPOINT_NUMBER,ENDPOINT_VALUE from user_histograms where TABLE_NAME='TAB'
TABLE_NAME   COLUMN_NAME  ENDPOINT_NUMBER ENDPOINT_VALUE
------------ ------------ --------------- --------------
TAB          B                          0              1
TAB          B                          1          10000

**注意:**上述顯示列B上只有最大值,最小值兩條記錄分別對應端點號(endpoint_number)0和1,這種顯示說明列B沒有直方圖信息。 在沒有直方圖的情況下,在B列上進行等值查詢的時候,都是索引範圍掃描。

SQL>  select * from tab where b=5
Execution Plan
----------------------------------------------------------
Plan hash value: 157166354
-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |  1000 |  6000 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TAB       |  1000 |  6000 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | TAB_B_IDX |  1000 |       |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("B"=5)

執行統計信息收集,收集直方圖信息,查看執行效果。
由於列B唯一值的個數沒有超過254,因此這時產生的是等頻直方圖。
SQL> exec dbms_stats.gather_table_stats(‘HR’,‘TAB’,cascade=>true);
PL/SQL procedure successfully completed.
默認是對所有列分析直方圖

在B=1時候採用索引掃描,而B=5時候,已經採用全表掃描了,說明直方圖起了作用。

SQL> select * from tab where b=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 157166354
-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |     1 |     6 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TAB       |     1 |     6 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | TAB_B_IDX |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("B"=1) 
SQL> select * from tab where  b=5;

Execution Plan
----------------------------------------------------------
Plan hash value: 1995730731
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  9991 | 59946 |     6   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB  |  9991 | 59946 |     6   (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("B"=5)

查看此時的直方圖信息:

SQL> select TABLE_NAME,COLUMN_NAME,ENDPOINT_NUMBER,ENDPOINT_VALUE from user_tab_histograms where table_name='TAB';
TABLE_NAME   COLUMN_NAME  ENDPOINT_NUMBER ENDPOINT_VALUE
------------ ------------ --------------- --------------
TAB          B                          1              1
TAB          B                          2              2
TAB          B                          3              3
TAB          B                          4              4
TAB          B                       9995              5
TAB          B                       9996           9996
TAB          B                       9997           9997
TAB          B                       9998           9998
TAB          B                       9999           9999
TAB          B                      10000          10000

TAB          A                          0              1
TABLE_NAME   COLUMN_NAME  ENDPOINT_NUMBER ENDPOINT_VALUE
------------ ------------ --------------- --------------
TAB          A                          1          10000
12 rows selected.

其中EDNPOINT_NUMBER是累計值。EDNPOINT_VALUE是列的值。可以看出這種等頻直方圖統計的列的信息是非常精確的。它爲每一個列值分配了一個bucket。從執行計劃的ROWS部分也可以看出ORACLE計算出來的cardinality是9991,和實際的情況完全吻合。

如果想知道每一個列值對應的數量是多少,需要做一下簡單的減法運算:假如想知道列值等於5的個數,那麼可以通過:9995-4=9991得到。這就是ENDPOINT_NUMBER累計值的含義。

等高直方圖,當列上的數據不同值超過254時,Oracle將會默認將列上的數據劃分爲高度一致但頻度不一致的等高直方圖。

SQL> exec dbms_stats.gather_table_stats('HR','TAB',cascade=>true,
method_opt=>'FOR COLUMNS B SIZE 8);
PL/SQL procedure successfully completed.

由於列B有10個唯一值,通過上面的size 8可以強制ORACLE使用等高直方圖。

查看直方圖信息.

SQL> select TABLE_NAME,COLUMN_NAME,ENDPOINT_NUMBER,
ENDPOINT_VALUE from user_histograms where table_name='TAB';
TABLE_NAME   COLUMN_NAME  ENDPOINT_NUMBER ENDPOINT_VALUE
------------ ------------ --------------- --------------
TAB          B                          0              1
TAB          B                          7              5
TAB          B                          8          10000
TAB          A                          0              1
TAB          A                          1          10000

從查詢結果驚奇的發現只有三個桶0 7 8,原來ORACLE會自動省去EDNPOINT_VALUE值相同且ENDPOINT_NUMBER相鄰的桶的值。省去了桶(EDNPOINT_NUMBER)爲1 2 3 4 5 6 ,EDNPOINT_VALUE爲5的六條內容。
說明:在等高直方圖中,EDNPOINT_NUMBER代表桶號,這一點與等頻直方圖不同。再看等高直方圖下的執行計劃:

SQL>  select * from tab where b=5
Execution Plan
----------------------------------------------------------
Plan hash value: 1995730731
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  9982 | 59892 |     6   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB  |  9982 | 59892 |     6   (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("B"=5)

有執行計劃的ROWS部分,ORACLE計算出來的cardinality不是特別精確的。9991纔是精確值。而等頻直方圖可以精確到9991,因此可以說等頻直方圖比等高直方圖穩定,精確。

可是現實很多時候,列的唯一值是超過254的。只能使用等高直方圖了。

直方圖有創建就有刪除,如何刪除直方圖。
11g有如下方法可以直接刪除直方圖信息

dbms_stats.delete_column_stats(ownname => user,
tabname => 'T',
colname => 'VAL',
col_stat_type => 'HISTOGRAM');

至此文章。

【總結】

  1. 在Oracle中直方圖是一種對數據分佈質量情況進行描述的工具。一句話直方圖的作用:是幫助CBO選擇出更準確的執行計劃;
  2. 通常情況下在以下場合中建議使用直方圖:1、當Where子句引用了列值分佈存在明顯偏差的列時,這時應該使用直方圖來幫助優化器來修正執行路徑;如果查詢不引用該列,則創建直方圖沒有意義;2、當列值導致不正確的判斷時,這種情況通常會發生在多表連接時,添加直方圖經常會向優化器提供使用最佳聯接方法所需的信息。
  3. 通過實驗介紹了兩種形式的直方圖:等頻直方圖與等高直方圖。

【參考】
http://www.itpub.net/thread-1350285-1-1.html
【參考】
http://chenxy.blog.51cto.com/729966/743065

以下是個人微信公衆號“一森咖記”,歡迎關注
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章