淺析Oracle統計信息

oracle優化器(Optimizer)

 

優化器對於每一位從業人士都不陌生,它是oracle數據庫內置的核心子系統,是數據庫的核心,可以說優化器是數據庫的一個靈魂,它在一定程度上決定了數據庫的效率。

在oracle數據庫中有一個參數OPTIMIZER_MODE控制着該數據庫的優化器模式,我們可以選擇不同方式的優化器。

①RULE

此模式是RBO優化器。

②CHOOSE

oracle 9i中默認的參數,oracle解析SQL使用什麼優化器取決於目標表有沒有統計信息。

③FIRST_ROWS_n(n=1101001000)

改參數值爲1-1101001000之間的任何一個值時,oracle使用CBO優化器,且oracle處理SQL的優先級取決於這個值在這個區間的先後順序。

④FIRST_ROWS

oracle聯合使用CBO和RBO來解析SQL,一般使用CBO解析SQL,且oracle會優先解析該值靠前的SQL文本,如果出現特定情況時使用RBO解析SQL。該參數在9i中已經過時。

⑤ALL_ROWS

oracle10g以後默認的優化器模式,基於成本的優化器,也是目前工作中遇到最多的默認優化器,當然仍然還有些老的系統使用RBO優化器,但不可否認CBO已經佔據了整個oracle數據庫行業。

 

根據優化器的判斷原則可分爲RBO 和CBO兩種優化器。

 

RBO(Rule-Based Optimizer):

RBO是通過oracle數據庫中的硬編碼實現內置的一組判斷規則。所有的目標SQL均是通過該規則生成優化器認爲最優的執行計劃。10g以後oracle建議使用CBO作爲默認優化器。

RBO中將規則分爲15個等級:

Path 1: Single Row by Rowid

Path 2: Single Row by Cluster Join

Path 3: Single Row by Hash Cluster Key with Unique or Primary Key

Path 4: Single Row by Unique or Primary Key

Path 5: Clustered Join

Path 6: Hash Cluster Key

Path 7: Indexed Cluster Key

Path 8: Composite Index

Path 9: Single-Column Indexes

Path 10: Bounded Range Search on Indexed Columns

Path 11: Unbounded Range Search on Indexed Columns

Path 12: Sort Merge Join

Path 13: MAX or MIN of Indexed Column

Path 14: ORDER BY on Indexed Column

Path 15: Full Table Scan

 

缺陷:

①RBO是通過oracle內部硬編碼來對目標SQL選擇合適的執行計劃,換句話說此方法並沒有考慮到目標表的實際數據量和數據分佈,也沒有考慮到物理設備IO和服務器CPU等相關資源的等待和消耗,僅僅是根據自身的判斷等級來判斷SQL的執行計劃。實際上並不適用所有的系統語句,一旦發生執行計劃選擇問題很難對其作出調整。

②RBO模式下對SQL的書寫要求極高,甚至目標表出現的先後順序不同都會出現不同的執行計劃,這無異於對開發維護人員提出了更高的要求。

③RBO模式下10G以後版本的新特性都不能使用,例如統計信息、直方圖和動態採樣,這些新特性都給CBO選擇最優的實際可行的執行計劃帶來了巨大的幫助。

 

RBO選錯執行計劃解決方式:

①對SQL中的謂詞條件做字符類型改變。假如一條SQL,我們需要查詢一些數據,而走FULL SCAN的效率要高於INDEX RANG SCAN回表的效率,但是RBO模式下索引等級高於全表掃描等級,會用索引範圍掃描,這是一個很糟糕的執行計劃。我們可以通過修改索引列謂詞的字符類型讓這條SQL不走索引來提高效率。當然此類型改寫方式多種多樣,這裏不再詳細討論。

②RBO中兩個同等級的執行計劃在數據字典中的緩存順序也會影響實際的執行計劃。在RBO中如果一條語句謂詞中涉及兩個索引,但是兩個索引使用的先後順序又嚴重影響SQL的執行效率。爲了使SQL最先走IND_A,可對本來創建較早的IND_A進行重建。RBO會優先使用最新的索引。

③RBO中多表關聯時,執行計劃等級相同的表關聯時,RBO驅動表選擇是從右往左選擇的,即最右側的等級值表爲驅動表。可以通過修改SQL文本中表的順序來調整執行計劃。注意:如果關聯表的等級值不同,無論怎麼調整SQL文本中表的順序都不能改變執行計劃。

④通過hint來強制改變SQL的執行計劃來達到理想的執行效率,這無異於是DBA的終極殺手鐗。

 

由於在當前生產環境中RBO的環境已經相當罕見了,故這裏不再過多的深入討論。

請注意:oracle中嚴格意義上是沒有驅動表和被驅動表概念的,爲了方便理解這裏引用此概念。

 

CBO(Cost-Based Optimizer):

上邊討論了RBO的一些缺陷,顯然RBO已經不能適用當前的生產環境了,從oracle 10G開始oracle默認使用CBO優化器,但是並沒有刪除RBO的相關代碼(依然可以使用),同時爲了強化CBO更明顯突出其中的“成本”,10G以後版本oracle 引入了統計信息和直方圖等新特性。

CBO中的Cost:實際上是根據oracle的相關統計信息計算出來的一個值,包含對應執行步驟的CPU、IO以及部分環境下的網絡消耗成本。CBO認爲減少CPU和IO的消耗是提升SQL執行效率的最重要的方式。

 

結果集(ROW Source):是指在施加謂詞之後的結果數量,當一條複雜的SQL進行解析時,CBO會首先對SQL進行查詢轉換改寫SQL爲簡單的SQL文本(關於查詢轉換會在之後詳細討論),每一個簡單的SQL文本都會產生一個結果集,多個結果集進行關聯處理,最終得到的查詢結果即爲最終的結果集。很顯然,此過程的結果集越小,oracle處理該語句的成本越低,執行SQL越高效。

 

集的勢(Cardinality):CBO特有概念,實際上是對目標SQL具體執行結果集包含數量的估算,這個值影響整條SQL的執行效率,該值越小CBO認爲執行計劃越優。

 

可選擇率(Selectivity):CBO特有概念,是指施加謂詞條件之後佔未施加謂詞條件的比率,即:0<Selectivity<1。

                施加謂詞的結果集數量(Cardinality)         1

Selectivity=------------------------------------ = -------------

                未施加謂詞的結果集數量(該列總數)      NUM_DISTINCT

 

顯然該值越小,甚至趨近於0,CBO認爲該執行計劃越優。

從以下實例中可以看到 ROW Source=6 ,Cardinality=Rows=6 Selectivity=Cardinality/總數=6/107

SYS@PROD1>  exec dbms_stats.gather_schema_stats('hr');

PL/SQL procedure successfully completed.

SYS@PROD1> select count(*) from hr.EMPLOYEES;

  COUNT(*)
----------
       107

SYS@PROD1> set autotrace on;
SYS@PROD1> set linesize 200;
SYS@PROD1> select count(*) from hr.EMPLOYEES where SALARY=2500;

  COUNT(*)
----------
         6

Execution Plan
----------------------------------------------------------
Plan hash value: 1756381138

--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |     1 |     4 |     3   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |           |     1 |     4 |            |          |
|*  2 |   TABLE ACCESS FULL| EMPLOYEES |     6 |    24 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

   2 - filter("SALARY"=2500)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        422  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

 

可傳遞性(Transtivity):CBO特有概念,是在SQL進行查詢轉換時所做的第一件事--對目標SQL進行等價改寫(僅適用於CBO)

①簡單謂詞傳遞.例如:a.id=b.id and b.id=2018,CBO會等價改寫爲a.id=2018。

②連接謂詞傳遞.例如:a.id=b.id and b.id=c.id,CBO會等價改寫爲a.id=c.id。

③外連接謂詞傳遞.例如:a.id=b.id(+) and a.id=2018 ,CBO會等價改寫爲a.id=b.id(+) and a.id=2018 and b.id(+)=2018.

 

當然CBO也不是很完善,隨着數據市場的複雜性加深,CBO缺陷也逐漸暴露,在實際生產出也出現了諸多問題。

缺陷:

①CBO默認where條件中出現的各個列之間是相互獨立的,沒有關聯關係。CBO會依據這個前提來估算Selectivity、Cardinality,進而生成執行計劃。而現實生產中多表之間並不全完是毫無關聯關係的,從而使產生的執行計劃有較大的偏差。目前緩解此問題的方法是通過動態採樣的方式來解決,但是動態採樣取決於參與動態採樣的數量和質量,而多列統計信息並不適用多表關聯情形。

②CBO會假設所有目標SQL單獨執行互不干擾。SQL語句訪問的索引塊和數據塊可能已經存放在BUFFER CACHE 中,若CBO還按照SQL單獨執行產生執行計劃,則產生執行計劃的Cost會高於實際Cost。

③CBO對直方圖也有諸多限制。

1>oracle 12c之前,Frequency類型的直方圖對應的bucket數量不能超過254,如果目標列distinct 的數量超過254,oracle就會使用 Height Balance類型直方圖,而Height Balance類型直方圖不會記錄所有的nonpoplar value,這種模式下極易選錯執行計劃。在12c以後DBMS_STATS.GATHER_TABLE_STATS函數estimate_percent 參數默認值爲AUTO_SAMPLE_SIZE,若該目標列的NUM_DISTINCE>buskets時就會成爲TOP-Frequency和Hybrid類型的直方圖。

2>oracle數據庫裏針對文本類型的字段收集直方圖時默認將該字段的前32個字符(實際上是15個字符)轉換成浮點數。將該浮點數作爲該列的統計信息存放在DBA_TAB_HISTOGRAMS數據字典中,這就會造成如果一條SQL前邊的字段內容一樣,只有後邊的不一樣時,CBO會認爲是一條SQL進而選錯執行計劃。

④CBO在多表關聯時可能會漏選執行計劃。oracle中幾個甚至十幾個表的關聯語句隨處可見,N個表關聯理論上出現的關聯可能性爲n!,實際上在oracle11g中這種關聯可能性受參數_OPTIMIZER_MAX_PERMUTATIONS所限制,它決定了多表關聯最多的可能性,這種情況下在一些多表關聯時會漏選執行計劃,而漏選的可能就是最優的執行計劃。

 

 

統計信息(STATISTICS)

在當前CBO模式下的oracle數據庫中,統計信息就尤爲重要,如果說CBO是一個指揮官,那麼統計信息就好比一個情報官,來定時或者不定時的爲CBO提供最新的情報,使CBO能夠做出正確的判斷。

統計信息實際上是oracle從多個維度描述數據庫對象(記錄數、塊數等)的一組信息,oracle並將該信息存儲在數據字典表中,以供CBO和DBA使用和查閱。ORACLE 10G之後在創建表之後會自動收集統計信息。它可以對錶、索引、列、系統、數據字典和內部對象收集統計信息。

表的統計信息:描述表的記錄數、塊數和平均行長

索引的統計信息:描述索引的深度、數據塊數量、聚簇因子。

列的統計信息:描述列的null、distinct數量。

系統的統計信息:描述服務器系統的處理能力,包含IO和CPU。

數據字典的統計信息:描述數據字典基表(TAB$和IND$)、數據字典基表索引。

內部對象的統計信息:描述數據字典內部表(X$系列表)的詳細信息。

 

收集統計信息的方法:

①ANALYZE TABLE table_name COMPUTE STATISTICS;

1>對錶employees以估算模式收集統計信息,採樣比例爲15%

analyze table hr.employees estimate statistics sample 15 percent for table;

    SYS@PROD1> analyze table hr.employees estimate statistics sample 15 percent for table;

    Table analyzed.
		
    ***********
    Table Level
    ***********
    
    
    Table                   Number                 Empty Average    Chain Average Global User           Sample Date
    Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
    --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
    EMPLOYEES                  107        5            3   6,505        0      71 NO     NO                107 08-10-2019
    
    Column                    Column                       Distinct          Number     Number Global User           Sample Date
    Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
    ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
    EMPLOYEE_ID               NUMBER(6,0) NOT NULL                                             NO     NO
    FIRST_NAME                VARCHAR2(20)                                                     NO     NO
    LAST_NAME                 VARCHAR2(25) NOT NULL                                            NO     NO
    EMAIL                     VARCHAR2(25) NOT NULL                                            NO     NO
    PHONE_NUMBER              VARCHAR2(20)                                                     NO     NO
    HIRE_DATE                 DATE NOT NULL                                                    NO     NO
    JOB_ID                    VARCHAR2(10) NOT NULL                                            NO     NO
    SALARY                    NUMBER(8,2)                                                      NO     NO
    COMMISSION_PCT            NUMBER(2,2)                                                      NO     NO
    MANAGER_ID                NUMBER(6,0)                                                      NO     NO
    DEPARTMENT_ID             NUMBER(4,0)                                                      NO     NO
    
                                  B                                        Average     Average
    Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
    Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
    --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
    EMP_EMAIL_UK    UNIQUE                                                                                  YES    NO
    EMP_EMP_ID_PK   UNIQUE                                                                                  YES    NO
    EMP_DEPARTMENT_ NONUNIQUE                                                                               YES    NO
    IX
    
    EMP_JOB_IX      NONUNIQUE                                                                               YES    NO
    EMP_MANAGER_IX  NONUNIQUE                                                                               YES    NO
    EMP_NAME_IX     NONUNIQUE                                                                               YES    NO
    
    Index           Column                     Col Column
    Name            Name                       Pos Details
    --------------- ------------------------- ---- ------------------------
    EMP_DEPARTMENT_ DEPARTMENT_ID                1 NUMBER(4,0)
    IX
    
    EMP_EMAIL_UK    EMAIL                        1 VARCHAR2(25) NOT NULL
    EMP_EMP_ID_PK   EMPLOYEE_ID                  1 NUMBER(6,0) NOT NULL
    EMP_JOB_IX      JOB_ID                       1 VARCHAR2(10) NOT NULL
    EMP_MANAGER_IX  MANAGER_ID                   1 NUMBER(6,0)
    EMP_NAME_IX     LAST_NAME                    1 VARCHAR2(25) NOT NULL
                    FIRST_NAME                   2 VARCHAR2(20)
    		

2>只對表以計算模式收集統計信息

analyze table hr.employees compute statistics for table;

    SYS@PROD1> analyze table hr.employees  compute statistics for table;
    
    Table analyzed.

3>對錶列以計算模式收集統計信息

analyze table hr.empoloyees compute statistics for columns EMPLOYEE_ID SALARY;

    SYS@PROD1> analyze table hr.employees  compute statistics for table;

    Table analyzed.

4>對索引以計算模式收集統計信息

analyze index hr.EMP_EMP_ID_PK compute statistics;

    SYS@PROD1> analyze index hr.EMP_EMP_ID_PK compute statistics;
        
    Index analyzed.

5>刪除表所有列、索引的統計信息

analyze table hr.employees delete statistics;

    SYS@PROD1> analyze table hr.employees delete statistics;

    Table analyzed.

6>一次性收集表的所有統計信息

analyze table hr.employees compute statistics;

    SYS@PROD1> analyze table hr.employees compute statistics;
    
    Table analyzed. 
    
    Table                   Number                 Empty Average    Chain Average Global User           Sample Date
    Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
    --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
    EMPLOYEES                  107        5            3   6,505        0      71 NO     NO                107 08-10-2019
    
    Column                    Column                       Distinct          Number     Number Global User           Sample Date
    Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
    ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
    EMPLOYEE_ID               NUMBER(6,0) NOT NULL              107       0       1          0 NO     NO                107 08-10-2019
    FIRST_NAME                VARCHAR2(20)                       91       0       1          0 NO     NO                107 08-10-2019
    LAST_NAME                 VARCHAR2(25) NOT NULL             102       0       1          0 NO     NO                107 08-10-2019
    EMAIL                     VARCHAR2(25) NOT NULL             107       0       1          0 NO     NO                107 08-10-2019
    PHONE_NUMBER              VARCHAR2(20)                      107       0       1          0 NO     NO                107 08-10-2019
    HIRE_DATE                 DATE NOT NULL                      98       0       1          0 NO     NO                107 08-10-2019
    JOB_ID                    VARCHAR2(10) NOT NULL              19       0       1          0 NO     NO                107 08-10-2019
    SALARY                    NUMBER(8,2)                        58       0       1          0 NO     NO                107 08-10-2019
    COMMISSION_PCT            NUMBER(2,2)                         7       0       1         72 NO     NO                107 08-10-2019
    MANAGER_ID                NUMBER(6,0)                        18       0       1          1 NO     NO                107 08-10-2019
    DEPARTMENT_ID             NUMBER(4,0)                        11       0       1          1 NO     NO                107 08-10-2019
    
                                  B                                        Average     Average
    Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
    Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
    --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
    EMP_EMAIL_UK    UNIQUE        0    1            107            107           1           1           19 YES    NO                107 08-10-2019
    EMP_EMP_ID_PK   UNIQUE        0    1            107            107           1           1            2 YES    NO                107 08-10-2019
    EMP_DEPARTMENT_ NONUNIQUE     0    1             11            106           1           1            7 YES    NO                106 08-10-2019
    IX
    
    EMP_JOB_IX      NONUNIQUE     0    1             19            107           1           1            8 YES    NO                107 08-10-2019
    EMP_MANAGER_IX  NONUNIQUE     0    1             18            106           1           1            7 YES    NO                106 08-10-2019
    EMP_NAME_IX     NONUNIQUE     0    1            107            107           1           1           15 YES    NO                107 08-10-2019
    
    Index           Column                     Col Column
    Name            Name                       Pos Details
    --------------- ------------------------- ---- ------------------------
    EMP_DEPARTMENT_ DEPARTMENT_ID                1 NUMBER(4,0)
    IX
    
    EMP_EMAIL_UK    EMAIL                        1 VARCHAR2(25) NOT NULL
    EMP_EMP_ID_PK   EMPLOYEE_ID                  1 NUMBER(6,0) NOT NULL
    EMP_JOB_IX      JOB_ID                       1 VARCHAR2(10) NOT NULL
    EMP_MANAGER_IX  MANAGER_ID                   1 NUMBER(6,0)
    EMP_NAME_IX     LAST_NAME                    1 VARCHAR2(25) NOT NULL
                    FIRST_NAME                   2 VARCHAR2(20)

②EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'owner',TABNAME=>'table_name');

1>收集表的統計信息

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES');

 

2>以估算方式收集表的統計信息,採樣比例爲15%

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',ESTIMATE_PERCENT=>15,METHOD_OPT=>'FOR TABLE',CASCADE=>FALSE);

METHOD_OPT=>'FOR TABLE'並不適用所有的oracle版本。估算模式的採樣比例改爲100或者null,就是以計算模式收集統計信息。

    SYS@PROD1> EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',ESTIMATE_PERCENT=>15,METHOD_OPT=>'FOR TABLE',CASCADE=>FALSE);
    SYS@PROD1> 
    
    Table                   Number                 Empty Average    Chain Average Global User           Sample Date
    Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
    --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
    EMPLOYEES                  107        5            0       0        0      69 YES    NO                107 08-10-2019
    
    Column                    Column                       Distinct          Number     Number Global User           Sample Date
    Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
    ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
    EMPLOYEE_ID               NUMBER(6,0) NOT NULL                                             NO     NO
    FIRST_NAME                VARCHAR2(20)                                                     NO     NO
    LAST_NAME                 VARCHAR2(25) NOT NULL                                            NO     NO
    EMAIL                     VARCHAR2(25) NOT NULL                                            NO     NO
    PHONE_NUMBER              VARCHAR2(20)                                                     NO     NO
    HIRE_DATE                 DATE NOT NULL                                                    NO     NO
    JOB_ID                    VARCHAR2(10) NOT NULL                                            NO     NO
    SALARY                    NUMBER(8,2)                                                      NO     NO
    COMMISSION_PCT            NUMBER(2,2)                                                      NO     NO
    MANAGER_ID                NUMBER(6,0)                                                      NO     NO
    DEPARTMENT_ID             NUMBER(4,0)                                                      NO     NO
    
                                  B                                        Average     Average
    Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
    Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
    --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
    EMP_EMAIL_UK    UNIQUE                                                                                  NO     NO
    EMP_EMP_ID_PK   UNIQUE                                                                                  NO     NO
    EMP_DEPARTMENT_ NONUNIQUE                                                                               NO     NO
    IX
    
    EMP_JOB_IX      NONUNIQUE                                                                               NO     NO
    EMP_MANAGER_IX  NONUNIQUE                                                                               NO     NO
    EMP_NAME_IX     NONUNIQUE                                                                               NO     NO
    
    Index           Column                     Col Column
    Name            Name                       Pos Details
    --------------- ------------------------- ---- ------------------------
    EMP_DEPARTMENT_ DEPARTMENT_ID                1 NUMBER(4,0)
    IX
    
    EMP_EMAIL_UK    EMAIL                        1 VARCHAR2(25) NOT NULL
    EMP_EMP_ID_PK   EMPLOYEE_ID                  1 NUMBER(6,0) NOT NULL
    EMP_JOB_IX      JOB_ID                       1 VARCHAR2(10) NOT NULL
    EMP_MANAGER_IX  MANAGER_ID                   1 NUMBER(6,0)
    EMP_NAME_IX     LAST_NAME                    1 VARCHAR2(25) NOT NULL
                    FIRST_NAME                   2 VARCHAR2(20)

3>以計算方式對列收集統計信息

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',ESTIMATE_PERCENT=>100,METHOD_OPT=>'FOR COLUMNS SIZE 1 EMPLOYEE_ID SALARY',CASCADE=>FALSE);

 

4>以計算模式收集索引的統計信息

EXEC DBMS_STATS.GATHER_INDEX_STATS(OWNNAME=>'HR',INDNAME=>'EMP_EMP_ID_PK',ESTIMATE_PERCENT=>100);

    SYS@PROD1> EXEC DBMS_STATS.GATHER_INDEX_STATS(OWNNAME=>'HR',INDNAME=>'EMP_EMP_ID_PK',ESTIMATE_PERCENT=>100);		
    
    
    Table                   Number                 Empty Average    Chain Average Global User           Sample Date
    Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
    --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
    EMPLOYEES                  107        5            0       0        0      69 YES    NO                107 08-10-2019
    
    Column                    Column                       Distinct          Number     Number Global User           Sample Date
    Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
    ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
    EMPLOYEE_ID               NUMBER(6,0) NOT NULL              107       0       1          0 YES    NO                107 08-10-2019
    FIRST_NAME                VARCHAR2(20)                                                     NO     NO
    LAST_NAME                 VARCHAR2(25) NOT NULL                                            NO     NO
    EMAIL                     VARCHAR2(25) NOT NULL                                            NO     NO
    PHONE_NUMBER              VARCHAR2(20)                                                     NO     NO
    HIRE_DATE                 DATE NOT NULL                                                    NO     NO
    JOB_ID                    VARCHAR2(10) NOT NULL                                            NO     NO
    SALARY                    NUMBER(8,2)                        58       0       1          0 YES    NO                107 08-10-2019
    COMMISSION_PCT            NUMBER(2,2)                                                      NO     NO
    MANAGER_ID                NUMBER(6,0)                                                      NO     NO
    DEPARTMENT_ID             NUMBER(4,0)                                                      NO     NO
    
                                  B                                        Average     Average
    Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
    Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
    --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
    EMP_EMAIL_UK    UNIQUE                                                                                  NO     NO
    EMP_EMP_ID_PK   UNIQUE        0    1            107            107           1           1            2 YES    NO                107 08-10-2019
    EMP_DEPARTMENT_ NONUNIQUE                                                                               NO     NO
    IX
    
    EMP_JOB_IX      NONUNIQUE                                                                               NO     NO
    EMP_MANAGER_IX  NONUNIQUE                                                                               NO     NO
    EMP_NAME_IX     NONUNIQUE                                                                               NO     NO
    
    Index           Column                     Col Column
    Name            Name                       Pos Details
    --------------- ------------------------- ---- ------------------------
    EMP_DEPARTMENT_ DEPARTMENT_ID                1 NUMBER(4,0)
    IX
    
    EMP_EMAIL_UK    EMAIL                        1 VARCHAR2(25) NOT NULL
    EMP_EMP_ID_PK   EMPLOYEE_ID                  1 NUMBER(6,0) NOT NULL
    EMP_JOB_IX      JOB_ID                       1 VARCHAR2(10) NOT NULL
    EMP_MANAGER_IX  MANAGER_ID                   1 NUMBER(6,0)
    EMP_NAME_IX     LAST_NAME                    1 VARCHAR2(25) NOT NULL
                    FIRST_NAME                   2 VARCHAR2(20)

5>刪除表的統計信息

EXEC DBMS_STATS.DELETE_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES');

 

6>一次性收集表的所有統計信息

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',ESTIMATE_PERCENT=>100,CASCADE=>TRUE);

    SYS@PROD1> EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',ESTIMATE_PERCENT=>100,CASCADE=>TRUE);
    
    Table                   Number                 Empty Average    Chain Average Global User           Sample Date
    Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
    --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
    EMPLOYEES                  107        5            0       0        0      69 YES    NO                107 08-10-2019
    
    Column                    Column                       Distinct          Number     Number Global User           Sample Date
    Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
    ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
    EMPLOYEE_ID               NUMBER(6,0) NOT NULL              107       0       1          0 YES    NO                107 08-10-2019
    FIRST_NAME                VARCHAR2(20)                       91       0       1          0 YES    NO                107 08-10-2019
    LAST_NAME                 VARCHAR2(25) NOT NULL             102       0       1          0 YES    NO                107 08-10-2019
    EMAIL                     VARCHAR2(25) NOT NULL             107       0       1          0 YES    NO                107 08-10-2019
    PHONE_NUMBER              VARCHAR2(20)                      107       0       1          0 YES    NO                107 08-10-2019
    HIRE_DATE                 DATE NOT NULL                      98       0       1          0 YES    NO                107 08-10-2019
    JOB_ID                    VARCHAR2(10) NOT NULL              19       0       1          0 YES    NO                107 08-10-2019
    SALARY                    NUMBER(8,2)                        58       0      58          0 YES    NO                107 08-10-2019
    COMMISSION_PCT            NUMBER(2,2)                         7       0       1         72 YES    NO                 35 08-10-2019
    MANAGER_ID                NUMBER(6,0)                        18       0       1          1 YES    NO                106 08-10-2019
    DEPARTMENT_ID             NUMBER(4,0)                        11       0       1          1 YES    NO                106 08-10-2019
    
                                  B                                        Average     Average
    Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
    Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
    --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
    EMP_EMAIL_UK    UNIQUE        0    1            107            107           1           1           19 YES    NO                107 08-10-2019
    EMP_EMP_ID_PK   UNIQUE        0    1            107            107           1           1            2 YES    NO                107 08-10-2019
    EMP_DEPARTMENT_ NONUNIQUE     0    1             11            106           1           1            7 YES    NO                106 08-10-2019
    IX
    
    EMP_JOB_IX      NONUNIQUE     0    1             19            107           1           1            8 YES    NO                107 08-10-2019
    EMP_MANAGER_IX  NONUNIQUE     0    1             18            106           1           1            7 YES    NO                106 08-10-2019
    EMP_NAME_IX     NONUNIQUE     0    1            107            107           1           1           15 YES    NO                107 08-10-2019
    
    Index           Column                     Col Column
    Name            Name                       Pos Details
    --------------- ------------------------- ---- ------------------------
    EMP_DEPARTMENT_ DEPARTMENT_ID                1 NUMBER(4,0)
    IX
    
    EMP_EMAIL_UK    EMAIL                        1 VARCHAR2(25) NOT NULL
    EMP_EMP_ID_PK   EMPLOYEE_ID                  1 NUMBER(6,0) NOT NULL
    EMP_JOB_IX      JOB_ID                       1 VARCHAR2(10) NOT NULL
    EMP_MANAGER_IX  MANAGER_ID                   1 NUMBER(6,0)
    EMP_NAME_IX     LAST_NAME                    1 VARCHAR2(25) NOT NULL
                    FIRST_NAME                   2 VARCHAR2(20)
    

ANALYZE和DBMS_STATS包的區別:

①ANALYZE不能夠準確收集分區表的統計信息。analyze 只會收集最底層的對象信息,然後彙總出高一層的對象信息,而對於統計信息這樣計算收集肯定會是不準卻的。

 

例如:

  SYS@PROD1>analyze table sh.sales compute statistics;

  Table                   Number                 Empty Average    Chain Average Global User           Sample Date
  Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
  --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
  SALES                  918,843    1,769          263     472        0      33 NO     NO                  0 08-10-2019
  
  Column                    Column                       Distinct          Number     Number Global User           Sample Date
  Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
  ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
  PROD_ID                   NUMBER(22) NOT NULL                72       0       1          0 NO     NO                    08-10-2019
  CUST_ID                   NUMBER(22) NOT NULL             3,203       0       1          0 NO     NO                    08-10-2019
  TIME_ID                   DATE NOT NULL                   1,460       0       1          0 NO     NO                    08-10-2019
  CHANNEL_ID                NUMBER(22) NOT NULL                 4       0       1          0 NO     NO                    08-10-2019
  PROMO_ID                  NUMBER(22) NOT NULL                 3       0       1          0 NO     NO                    08-10-2019
  QUANTITY_SOLD             NUMBER(10,2) NOT NULL               1       1       1          0 NO     NO                    08-10-2019
  AMOUNT_SOLD               NUMBER(10,2) NOT NULL             867       0       1          0 NO     NO                    08-10-2019
  
                                B                                        Average     Average
  Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
  Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
  --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
  SALES_PROD_BIX  NONUNIQUE     1   32             72          1,074           1           1        1,074 YES    NO                  0 08-10-2019
  SALES_CUST_BIX  NONUNIQUE     1  475          3,203         35,808           1           1       35,808 YES    NO                  0 08-10-2019
  SALES_TIME_BIX  NONUNIQUE     1   59          1,460          1,460           1           1        1,460 YES    NO                  0 08-10-2019
  SALES_CHANNEL_B NONUNIQUE     1   47              4             92           1           1           92 YES    NO                  0 08-10-2019
  IX
  
  SALES_PROMO_BIX NONUNIQUE     1   30              3             54           1           2           54 YES    NO                  0 08-10-2019
  
  Index           Column                     Col Column
  Name            Name                       Pos Details
  --------------- ------------------------- ---- ------------------------
  SALES_CHANNEL_B CHANNEL_ID                   1 NUMBER(22) NOT NULL
  IX
  
  SALES_CUST_BIX  CUST_ID                      1 NUMBER(22) NOT NULL
  SALES_PROD_BIX  PROD_ID                      1 NUMBER(22) NOT NULL
  SALES_PROMO_BIX PROMO_ID                     1 NUMBER(22) NOT NULL
  SALES_TIME_BIX  TIME_ID                      1 DATE NOT NULL
  ....此處分區信息過多 省略....
  
  SYS@PROD1>EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'SH',TABNAME=>'SALES',ESTIMATE_PERCENT=>100,CASCADE=>TRUE);
  
  Table                   Number                 Empty Average    Chain Average Global User           Sample Date
  Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
  --------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
  SALES                  918,843    1,769            0       0        0      29 YES    NO            918,843 08-10-2019
  
  Column                    Column                       Distinct          Number     Number Global User           Sample Date
  Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
  ------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
  PROD_ID                   NUMBER(22) NOT NULL                72       0       1          0 YES    NO            918,843 08-10-2019
  CUST_ID                   NUMBER(22) NOT NULL             7,059       0       1          0 YES    NO            918,843 08-10-2019
  TIME_ID                   DATE NOT NULL                   1,460       0       1          0 YES    NO            918,843 08-10-2019
  CHANNEL_ID                NUMBER(22) NOT NULL                 4       0       1          0 YES    NO            918,843 08-10-2019
  PROMO_ID                  NUMBER(22) NOT NULL                 4       0       1          0 YES    NO            918,843 08-10-2019
  QUANTITY_SOLD             NUMBER(10,2) NOT NULL               1       1       1          0 YES    NO            918,843 08-10-2019
  AMOUNT_SOLD               NUMBER(10,2) NOT NULL           3,586       0       1          0 YES    NO            918,843 08-10-2019
  
                                B                                        Average     Average
  Index                      Tree Leaf       Distinct         Number Leaf Blocks Data Blocks      Cluster Global User           Sample Date
  Name            Unique    Level Blks           Keys        of Rows     Per Key     Per Key       Factor Stats  Stats            Size MM-DD-YYYY
  --------------- --------- ----- ---- -------------- -------------- ----------- ----------- ------------ ------ ------ -------------- ----------
  SALES_PROD_BIX  NONUNIQUE     1   32             72          1,074           1          14        1,074 YES    NO              1,074 08-10-2019
  SALES_CUST_BIX  NONUNIQUE     1  475          7,059         35,808           1           5       35,808 YES    NO             35,808 08-10-2019
  SALES_TIME_BIX  NONUNIQUE     1   59          1,460          1,460           1           1        1,460 YES    NO              1,460 08-10-2019
  SALES_CHANNEL_B NONUNIQUE     1   47              4             92          11          23           92 YES    NO                 92 08-10-2019
  IX
  
  SALES_PROMO_BIX NONUNIQUE     1   30              4             54           7          13           54 YES    NO                 54 08-10-2019
  
  Index           Column                     Col Column
  Name            Name                       Pos Details
  --------------- ------------------------- ---- ------------------------
  SALES_CHANNEL_B CHANNEL_ID                   1 NUMBER(22) NOT NULL
  IX
  
  SALES_CUST_BIX  CUST_ID                      1 NUMBER(22) NOT NULL
  SALES_PROD_BIX  PROD_ID                      1 NUMBER(22) NOT NULL
  SALES_PROMO_BIX PROMO_ID                     1 NUMBER(22) NOT NULL
  SALES_TIME_BIX  TIME_ID                      1 DATE NOT NULL
  
  ....此處分區信息過多 省略....

通過對比以上兩種方式收集分區表sh.sales統計信息可以發現,analyze方式收集統計信息對列distinct的值並不準確,dbms包收集的是準確的。

  SH@PROD1> select table_name,PARTITIONED from user_tables where table_name='SALES';
 
  Table_Name            PAR
  ---------------	---
  SALES          	YES  
  SH@PROD1> select count(*) from sales;
  
    COUNT(*)
  ----------
      918843
  SH@PROD1> select count(distinct CUST_ID) from sales;
  
  COUNT(DISTINCTCUST_ID)
  ----------------------
                    7059
  SH@PROD1> select count(distinct AMOUNT_SOLD) from sales;
  
  COUNT(DISTINCTAMOUNT_SOLD)
  --------------------------
                        3586
	

②analyze不能夠並行的收集統計信息,隨着數據市場的擴大,在海量數據庫裏收集統計信息的時間無異於是一個值得關注的問題,時間過長會對數據庫性能造成一定的影響,而這一點analyze是不能解決的。

③以上兩點是在實際生產中analyze帶來的明顯缺陷,而這一點dbms_stats包就可以完美的解決(degree=>4開啓並行),這也是在oracle建議使用dbms_stats包收集統計信息的重要原因。

 

自動收集統計信息:

oracle 10g以前是需要DBA手動寫腳本來收集統計信息的,在oracle 10g中的自動收集統計信息作業爲GATHER_STATS_JOB,實際上是調用dms_stats_internal.update_target_list存儲過程來實現的(失效標準也在其中)。

SYS@PROD1>SELECT JOB_NAME,PROGRAM_NAME,SCHEDULE_NAME FROM DBA_SCHEDULER_JOBS WHERE JOB_NAME='GATHER_STATS_JOB';

JOB_NAME              PROGRAM_NAME                     SCHEDULE_NAME
------------------    ---------------------------      -----------------------------
GATHER_STATS_JOB      GATHER_STATS_PROG                MAINTENANCE_WINDOW_GROUP

SYS@PROD1>select * from dba_scheduler_wingroup_members where window_group_name='MAINTENANCE_WINDOW_GROUP';

WINDOW_GROUP_NAME              WINDOW_NAME
------------------------------ ------------------------------
MAINTENANCE_WINDOW_GROUP       WEEKNIGHT_WINDOW
MAINTENANCE_WINDOW_GROUP       WEEKEND_WINDOW

10g自動作業特點:

①每天通過執行GATHER_STATS_JOB作業來實現,其本質是調用dbms_stats包裏的存儲過程GATHER_DATABASE_STATS_JOB_PROC。

②WEEKEND_WINDOW:工作日運行,每天22點開始,最長8小時。

WEEKNIGHT_WINDOW:週六早晨0點開始,最長24小時。

③沒有資源限制設置,可無限制消耗資源。

10g缺點:

①可配置維護窗口少,不夠靈活。

②沒有資源限制設置,這可能會嚴重影響數據庫的正常業務運行。

 

11g的自動收集作業:

 

11g中引入了gather_stats_prog自動任務用來自動收集統計信息,自動任務對應的客戶端名稱爲auto optimizer stats collection。在自動運行任務gather_stats_prog時每次都會先生成ORA$AT_OS_OPT_XXX的作業然後在執行這個作業。

SYS@PROD1> select client_name,task_name,operation_name,status from dba_autotask_task;

CLIENT_NAME                         TASK_NAME                 OPERATION_NAME            STATUS
----------------------------------- ------------------------- ------------------------- --------
sql tuning advisor                  AUTO_SQL_TUNING_PROG      automatic sql tuning task ENABLED
auto space advisor                  auto_space_advisor_prog   auto space advisor job    ENABLED
auto optimizer stats collection     gather_stats_prog         auto optimizer stats job  ENABLED

SYS@PROD1> select * from dba_scheduler_wingroup_members where window_group_name='MAINTENANCE_WINDOW_GROUP';

WINDOW_GROUP_NAME              WINDOW_NAME
------------------------------ ------------------------------
MAINTENANCE_WINDOW_GROUP       MONDAY_WINDOW
MAINTENANCE_WINDOW_GROUP       TUESDAY_WINDOW
MAINTENANCE_WINDOW_GROUP       WEDNESDAY_WINDOW
MAINTENANCE_WINDOW_GROUP       THURSDAY_WINDOW
MAINTENANCE_WINDOW_GROUP       FRIDAY_WINDOW
MAINTENANCE_WINDOW_GROUP       SATURDAY_WINDOW
MAINTENANCE_WINDOW_GROUP       SUNDAY_WINDOW

11g自動作業特點:

①自動運行作業每天通過運行gather_stats_prog來實現,每次都會先生成ORA$AT_OS_OPT_XXX的作業然後在執行這個作業,實際上是調用DBMS_STATS包裏的存儲過程GATHER_DATABASE_STATS_JOB_PROC。

②MONDAY_WINDOW - FRIDAY_WINDOW:每天22點運行,最長4小時;

SATURDAY_WINDOW,SUNDAY_WINDOW:每天6點運行,最長20小時。

③oracle對作業執行施加了資源組:DEFAULT_MAINIENANCE_PLAN。

 

oracle自動收集統計信息判斷標準:

自上次統計信息收集作業完成之後,若MON_MODS_ALL$中記錄的delete+insert+update記錄數超過TAB$記錄數的10%,或者上次統計信息收集完成後目標表執行過truncate,那麼oracle認爲此時統計信息已失效。

 

表的統計信息:

表的統計信息實際上是存放在TAB$,TABPART$,TABSUBPART$等數據字典基表中。可以通過DBA_TABLES,DBA_TAB_PARTITIONS,DBA_TAB_SUBPARTITIONS數據字典分別查看統計信息。

TEST@PROD1> create  table ren_test(id number,name varchar2(10));

DECLARE
i number;
BEGIN
FOR i in 1..10000 loop 
INSERT INTO ren_test(ID, NAME) VALUES(10, 'CAP');
COMMIT;
END LOOP;
END;
/
TEST@PROD1> select count(*) from ren_test;

  COUNT(*)
----------
     10000
計算Average row length長度
SYS@PROD1> select dump(10,16) from dual; --2+1(描述)

DUMP(10,16)
-----------------
Typ=2 Len=2: c1,b
SYS@PROD1> select dump('CAP',16) from dual;--3+1(描述)

DUMP('CAP',16)
----------------------
Typ=96 Len=3: 43,41,50     
查看統計信息:  Average Row Len: 3+4=7
SYS@PROD1>  exec dbms_stats.gather_table_stats('test','ren_test');

Table                   Number                 Empty Average    Chain Average Global User           Sample Date
Name                   of Rows   Blocks       Blocks   Space    Count Row Len Stats  Stats            Size MM-DD-YYYY
--------------- -------------- -------- ------------ ------- -------- ------- ------ ------ -------------- ----------
REN_TEST                10,000       20            0       0        0       7 YES    NO             10,000 08-10-2019

Column                    Column                       Distinct          Number     Number Global User           Sample Date
Name                      Details                        Values Density Buckets      Nulls Stats  Stats            Size MM-DD-YYYY
------------------------- ------------------------ ------------ ------- ------- ---------- ------ ------ -------------- ----------
ID                        NUMBER(22)                          1       1       1          0 YES    NO             10,000 08-10-2019
NAME                      VARCHAR2(10)                        1       1       1          0 YES    NO             10,000 08-10-2019

索引的統計信息:

索引的統計信息實際上是存放在IND$,ENDPART$,INDSUBPART$等數據字典基表中。可以通過DBA_INDEXES,DBA_IND_PARTITIONS,DBA_IND_SUBPARTITIONS數據字典分別查看統計信息。

left_block字段存儲的目標索引的葉子塊的數量,CBO通過這個字段來判斷使用什麼索引掃描方式,left_block葉子塊數量越多則索引掃描的成本越高。

通過以下實例可以看出,在數據量減少之後索引重建會減少B tree的深度,oracle中使用索引掃描索引深度也是影響索引效率的一個重要指標。

TEST@PROD1> select owner,table_name,index_name,BLEVEL,LEAF_BLOCKS,DISTINCT_KEYS,CLUSTERING_FACTOR,STATUS,NUM_ROWS,GLOBAL_STATS from dba_indexes where owner='TEST' and table_name='REN_TEST';

                               Table           Index                      Leaf       Distinct      Cluster                  Number Global
OWNER                          Name            Name                BLEVEL Blks           Keys       Factor STATUS          of Rows Stats
------------------------------ --------------- --------------- ---------- ---- -------------- ------------ -------- -------------- ------
TEST                           REN_TEST        IND_ID                   1   20              1           17 VALID            10,000 NO
TEST@PROD1> 
TEST@PROD1> analyze index IND_ID validate structure;
TEST@PROD1> 
TEST@PROD1> select name,height,lf_rows,lf_blks,del_lf_rows from index_stats;

NAME                               HEIGHT    LF_ROWS    LF_BLKS DEL_LF_ROWS
------------------------------ ---------- ---------- ---------- -----------
IND_ID                                  2      10000         20           0  --HEIGHT永遠比BLEVEL值大1
TEST@PROD1> delete from ren_test where id=10;
TEST@PROD1> commit;
TEST@PROD1> insert into ren_test values(100,'ren_1');
TEST@PROD1> commit;
TEST@PROD1> select count(*) from ren_test;

  COUNT(*)
----------
         1
TEST@PROD1> alter index ind_id rebuild;
TEST@PROD1> analyze index IND_ID validate structure;
TEST@PROD1> select name,height,lf_rows,lf_blks,del_lf_rows from index_stats;

NAME                               HEIGHT    LF_ROWS    LF_BLKS DEL_LF_ROWS
------------------------------ ---------- ---------- ---------- -----------
IND_ID                                  1          1          1           0

列的統計信息:

列的統計信息實際上是存放在HIST_HEAD$數據字典基表中。可以通過DBA_TAB_COL_STATISTICS,DBA_PART_COL_STATISTICS,DBA_SUBPART_COL_STATISTICS數據字典分別查看統計信息。

TEST@PROD1> create table test_obj as select * from dba_objects 
  2  ;
TEST@PROD1> select count(*) from test_obj;

  COUNT(*)
----------
     72462

TEST@PROD1> select  low_value ,high_value,num_distinct,num_nulls from  DBA_TAB_COL_STATISTICS where table_name='TEST_OBJ' and owner='TEST';

                                              Distinct     Number
LOW_VALUE            HIGH_VALUE                 Values      Nulls
-------------------- -------------------- ------------ ----------
C103                 C3085250                   72,462          0


TEST@PROD1>  select max(object_ID),dump(max(object_id),16) from test_obj;

MAX(OBJECT_ID)                  DUMP(MAX(OBJECT_ID),16)
------------------------------ ----------------------------------------
   78179                         Typ=2 Len=4: c3,8,52,50    --C3085250
TEST@PROD1> select min(object_ID),dump(min(object_id),16) from test_obj;

MIN(OBJECT_ID   )               DUMP(MIN(OBJECT_ID),16)
------------------------------ ----------------------------------------
      2                          Typ=2 Len=2: c1,3        --C103

數據字典中的LOW_VALUE、HIGH_VALUE分別存儲目標列的最小值和最大值,CBO通過這兩個參數來評估目標SQL在做範圍查詢時的selectivity。

在不考慮直方圖的影響下,目標列的selectivity遵循以下原則:

①column > val ,LOW_VALUE < VAL < HEIGHT_VALUE(範圍查詢時該列>指定的一個值)

selectivity=((HIGH_VALUE - VAL) / (HIGH_VALUE - LOW_VALUE)) * null_adjust

null_adjust=(NUM_ROES - NUM_NULLS) / NUM_ROES

 

可通過該公式計算以下執行計劃的dictionary。

  TEST@PROD1>  select count(*) from test_obj where object_id>26054;

    COUNT(*)
  ----------
     46893
  計算結果爲: 
  TEST@PROD1> select round((78179-26054)/(78179-2)*(72462-0)/72462*72462) from dual;
  
  ROUND((78179-26054)/(78179-2)*(72462-0)/72462*72462)
  ----------------------------------------------------
                                                 48314
  執行計劃爲:
  Execution Plan
  ----------------------------------------------------------
  Plan hash value: 2217143630
  
  -------------------------------------------------------------------------------
  | Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
  -------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT   |          |     1 |     5 |   289   (1)| 00:00:04 |
  |   1 |  SORT AGGREGATE    |          |     1 |     5 |            |          |
  |*  2 |   TABLE ACCESS FULL| TEST_OBJ | 48314 |   235K|   289   (1)| 00:00:04 |
  -------------------------------------------------------------------------------
  
  Predicate Information (identified by operation id):
  ---------------------------------------------------
  
     2 - filter("OBJECT_ID">26054)
  
  
  Statistics
  ----------------------------------------------------------
            1  recursive calls
            0  db block gets
         1037  consistent gets
            0  physical reads
            0  redo size
          424  bytes sent via SQL*Net to client
          419  bytes received via SQL*Net from client
            2  SQL*Net roundtrips to/from client
            0  sorts (memory)
            0  sorts (disk)
            1  rows processed      

②column < val ,LOW_VALUE < VAL < HEIGHT_VALUE

selectivity=((VAL - LOW_VALUE) / (HIGH_VALUE - LOW_VALUE)) * null_adjust

null_adjust=(NUM_ROES - NUM_NULLS) / NUM_ROES

 

可通過該公式計算以下執行計劃的dictionary。

  TEST@PROD1> select count(*) from test_obj where object_id<26054;

    COUNT(*)
  ----------
     25568
  計算結果爲:
  TEST@PROD1> select round((26054-2)/(78179-2)*(72462-0)/72462*72462) from dual;
  
  ROUND((26054-2)/(78179-2)*(72462-0)/72462*72462)
  ------------------------------------------------
                                             24148
  執行計劃爲:
  Execution Plan
  ----------------------------------------------------------
  Plan hash value: 2217143630
  
  -------------------------------------------------------------------------------
  | Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
  -------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT   |          |     1 |     5 |   289   (1)| 00:00:04 |
  |   1 |  SORT AGGREGATE    |          |     1 |     5 |            |          |
  |*  2 |   TABLE ACCESS FULL| TEST_OBJ | 24148 |   117K|   289   (1)| 00:00:04 |
  -------------------------------------------------------------------------------
  
  Predicate Information (identified by operation id):
  ---------------------------------------------------
  
     2 - filter("OBJECT_ID"<26054)
  
  
  Statistics
  ----------------------------------------------------------
            0  recursive calls
            0  db block gets
         1037  consistent gets
            0  physical reads
            0  redo size
          424  bytes sent via SQL*Net to client
          419  bytes received via SQL*Net from client
            2  SQL*Net roundtrips to/from client
            0  sorts (memory)
            0  sorts (disk)
            1  rows processed         

③column >= val ,LOW_VALUE < VAL < HEIGHT_VALUE

selectivity=((HIGH_VALUE - VAL) / (HIGH_VALUE - LOW_VALUE)+1 / NUM_DISTINCT) * null_adjust

null_adjust=(NUM_ROES - NUM_NULLS) / NUM_ROES

 

④column <= val ,LOW_VALUE < VAL < HEIGHT_VALUE

selectivity=((VAL - LOW_VALUE) / (HIGH_VALUE - LOW_VALUE)+1 / NUM_DISTINCT) * null_adjust

null_adjust=(NUM_ROES - NUM_NULLS) / NUM_ROES

 

⑤val1 < column < val2 ,LOW_VALUE < VAL1 <VAL2 < HEIGHT_VALUE

selectivity=((VAL2 - VAL1) / (HIGH_VALUE - LOW_VALUE)+2 / NUM_DISTINCT) * null_adjust

null_adjust=(NUM_ROES - NUM_NULLS) / NUM_ROES

 

通過該公式計算出以下執行計劃的dictionary。

  TEST@PROD1> select count(*) from test_obj where object_id between 26054 and 50276;
  
    COUNT(*)
  ----------
     24223     
  TEST@PROD1> select count(distinct object_id) from test_obj;
  
  COUNT(DISTINCTOBJECT_ID)
  ------------------------
                     72462     
  計算結果爲:
  TEST@PROD1> select round(((50276-26054)/(78179-2)+2/72462)*(72462-0)/72462*72462) from dual;
  
  ROUND(((50276-26054)/(78179-2)+2/72462)*(72462-0)/72462*72462)
  --------------------------------------------------------------
                                                           22453
執行計劃爲: 
 TEST@PROD1> select count(*) from test_obj where object_id between 26054 and 50276;
  
  Execution Plan
  ----------------------------------------------------------
  Plan hash value: 2217143630
  
  -------------------------------------------------------------------------------
  | Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
  -------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT   |          |     1 |     5 |   289   (1)| 00:00:04 |
  |   1 |  SORT AGGREGATE    |          |     1 |     5 |            |          |
  |*  2 |   TABLE ACCESS FULL| TEST_OBJ | 22453 |   109K|   289   (1)| 00:00:04 |
  -------------------------------------------------------------------------------
  
  Predicate Information (identified by operation id):
  ---------------------------------------------------
  
     2 - filter("OBJECT_ID"<=50276 AND "OBJECT_ID">=26054)
  
  
  Statistics
  ----------------------------------------------------------
            0  recursive calls
            0  db block gets
         1037  consistent gets
            0  physical reads
            0  redo size
          424  bytes sent via SQL*Net to client
          419  bytes received via SQL*Net from client
            2  SQL*Net roundtrips to/from client
            0  sorts (memory)
            0  sorts (disk)
            1  rows processed     

謂詞越界:

謂詞越界就是select的謂詞的條件不在統計信息low_value 和 high_value 之間,即不能使用上述公式來計算執行計劃的dictionary,這種情況下CBO評估出來的selectivity會出現嚴重的偏差,導致CBO選錯執行計劃。

下面做一組測試,先插入部分數據

DECLARE
i INT;
BEGIN
i := 78179;
WHILE(i < 100000)
LOOP
i := i + 1;
INSERT INTO test_obj(object_id) VALUES(i);
COMMIT;
END LOOP;
END;
/
查看此時的num_rows:
TEST@PROD1> select count(*) from test_obj;

  COUNT(*)
----------
     94283
TEST@PROD1> select max(object_ID),dump(max(object_id),16) from test_obj;

MAX(OBJECT_ID) DUMP(MAX(OBJECT_ID),16)
-------------- ----------------------------------------
        100000 Typ=2 Len=2: c3,b    
TEST@PROD1> select min(object_ID),dump(min(object_id),16) from test_obj;

MIN(OBJECT_ID   )               DUMP(MIN(OBJECT_ID),16)
------------------------------ ----------------------------------------
      2                          Typ=2 Len=2: c1,3        --C103
不收集統計信息,此時統計列統計信息過舊,HIGH_VALUE依然是原來的值78179
TEST@PROD1> select  low_value ,high_value,num_distinct,num_nulls from  DBA_TAB_COL_STATISTICS where table_name='TEST_OBJ' and owner='TEST';

                                                                  Distinct     Number
LOW_VALUE                      HIGH_VALUE                           Values      Nulls
------------------------------ ------------------------------ ------------ ----------
C103                           C3085250                             72,462(原值)  0

查詢結果返回2081行結果集。
TEST@PROD1> select count(*) from test_obj where object_id between 78200 and 81000;

  COUNT(*)
----------
      2801
計算結果爲:
TEST@PROD1>  select round(((81000-78200)/(100000-2)+2/94283)*(94283-0)/94283*94283) from dual; 

ROUND(((81000-78200)/(100000-2)+2/94283)*(94283-0)/94283*94283)
---------------------------------------------------------------
                                                           2642

查看結果集發現dictionary值爲1,這明顯是一個錯誤的執行計劃,由於統計信息過舊,已經低於謂詞條件區間(謂詞過界)導致CBO低估了查詢成本。

TEST@PROD1>  select count(*) from test_obj where object_id between 78200 and 81000;

Execution Plan
----------------------------------------------------------
Plan hash value: 2217143630

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 |     5 |   289   (1)| 00:00:04 |
|   1 |  SORT AGGREGATE    |          |     1 |     5 |            |          |
|*  2 |   TABLE ACCESS FULL| TEST_OBJ |     1 |     5 |   289   (1)| 00:00:04 |
-------------------------------------------------------------------------------

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

   2 - filter("OBJECT_ID">=78200 AND "OBJECT_ID"<=81000)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       1117  consistent gets
          0  physical reads
          0  redo size
        423  bytes sent via SQL*Net to client
        419  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

重新收集統計信息再次查看執行計劃。

TEST@PROD1> exec dbms_stats.gather_table_stats('test','test_obj');
TEST@PROD1> select  low_value ,high_value,num_distinct,num_nulls from  DBA_TAB_COL_STATISTICS where table_name='TEST_OBJ' and owner='TEST';

                                              Distinct     Number
LOW_VALUE            HIGH_VALUE                 Values      Nulls
-------------------- -------------------- ------------ ----------
C103                 C30B                       94,283          0

此時統計信息HIGH_VALUE已經和最初計算的值相等,Typ=2 Len=2: c3,b。再次查看執行計劃,此時CBO已經能夠產生了正確的執行計劃了。

執行計劃爲:

TEST@PROD1> select count(*) from test_obj where object_id between 78200 and 81000;

Execution Plan
----------------------------------------------------------
Plan hash value: 2217143630

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 |     5 |   314   (1)| 00:00:04 |
|   1 |  SORT AGGREGATE    |          |     1 |     5 |            |          |
|*  2 |   TABLE ACCESS FULL| TEST_OBJ |  2642 | 13210 |   314   (1)| 00:00:04 |
-------------------------------------------------------------------------------

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

   2 - filter("OBJECT_ID">=78200 AND "OBJECT_ID"<=81000)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       1117  consistent gets
          0  physical reads
          0  redo size
        423  bytes sent via SQL*Net to client
        419  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

 

 

直方圖:

關於CBO在一個目標表的成本計算在上邊已經做了討論,已經知道的結果是CBO會根據SQL施加where條件列的Cardinality和selectivity來計算一條執行計劃的cost,但是該方法在實際生產中是有侷限性的,使用該方式計算cost的前提是默認目標列內容是分佈均勻的,例如個人信息表的id和phone_number字段,每個列上的內容唯一且不爲空。但是我們知道在複雜的生產環境中,這種表列只是少數,大多數表的列是存在分佈不均甚至存在嚴重列傾斜的的現象,這種情況下再以Cardinality和selectivity爲原則判斷cost顯然已經不合理了,它會使CBO產生錯誤的執行計劃。

爲了防止此問題對oracle產生嚴重的影響,oracle引入了直方圖,它是一個詳細描述列數據分佈的特殊的列統計信息,實際存放在HISTGRM$基表中,可通過DBA_TAB_HISTOGRAMS、DBA_PART_HISTOGRAMS、DBA_SUBPART_HISTOGRAMS來分別查看列直方圖信息。oracle只對常用的目標列收集直方圖信息,oracle默認爲不常用的列不需要收集直方圖信息,SQL中的謂詞條件會存在SYS.COL_USAGE$基表中,在執行DBMS_STATS包時首先先查詢SYS.COL_USAGE$基表,只收集基表中有信息的列的直方圖信息。

oracle直方圖實際上是通過bucket(桶)的方式分別從ENDPOINT NUMBER和ENDPOINT VALUE兩個維度來描述目標列的數據分佈。分別對應DBA_TAB_HISTOGRAMS、DBA_PART_HISTOGRAMS、DBA_SUBPART_HISTOGRAMS中的ENDPOINT_NUMBER/BUCKET_NUMBER和ENDOINT_VALUE字段。還可通過DBA_TAB_COL_STATISTICS、DBA_PART_COL_STATISTICS和DBA_SUBPART_COL_STATISTICS查詢bucket總數。

ENDPOINT NUMBER是直方圖中bucket的編號,由DBMS.STATS包中的METHOD_OPT參數中的size控制buckets的數量(默認爲254),在oracle11g中規定該值不能大於254。

ENDPOINT VALUE是直方圖bucket的結束點,CBO可通過該值來準確計算目標結果集在目標列中的selectivity。

通過以下實例可以明顯看到直方圖信息對統計信息的影響:

TEST@PROD1> select id,count(*) from REN_TEST group by id;

        ID   COUNT(*)
---------- ----------
         1          1
        10       1000

我們可以發現REN_TEST已經出現了嚴重的列傾斜,不收集直方圖收集統計信息,可以查看執行計劃:

TEST@PROD1> select count(*) from ren_test where id=1; 

Execution Plan
----------------------------------------------------------
Plan hash value: 3666266488

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |     1 |     3 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |        |     1 |     3 |            |          |
|*  2 |   INDEX RANGE SCAN| IND_ID |   501 |  1503 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

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

   2 - access("ID"=1)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        422  bytes sent via SQL*Net to client
        419  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

可以看到此處的Cardinality爲501,這明顯是不對的,結果應爲1,這個錯誤的執行計劃產生的結果就是在列傾斜的情況下沒有直方圖信息CBO估算產生的,由於沒有直方圖信息,where條件ID列的selectivity是1/2,故該執行計劃的Cardinality爲501.

計算結果爲:

TEST@PROD1>  select round(1001*(1/2))from dual;

ROUND(1001*(1/2))
-----------------
              501

收集直方圖信息後再次查看執行計劃,發現此時已經是一個實際的值了。

TEST@PROD1>exec dbms_stats.gather_table_stats('test','ren_test',estimate_percent=>100,method_opt=>'for all columns size 245');
TEST@PROD1> select count(*) from ren_test where id=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3666266488

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |     1 |     3 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |        |     1 |     3 |            |          |
|*  2 |   INDEX RANGE SCAN| IND_ID |     1 |     3 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

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

   2 - access("ID"=1)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        422  bytes sent via SQL*Net to client
        419  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed  

直方圖類型:

在oracle 12c之前oracle直方圖類型爲Frequency和Height Balanced兩種類型(在oracle 12c中爲了解決Height Balanced不準確引起的oracle性能問題而引入了Top-Frequency和Hybrid類型的直方圖)。在oracle11g中如果目標列NUM_DISTINCT值的數量小於設定的Bucket的數量則使用Frequency類型直方圖,若目標列distinct值的數量大於Bucket的數量則使用Height Balanced類型直方圖。在oracle12c以後DBMS_STATS包的METHOD_OPT參數引入了AUTO_SAMPLE_SIZE參數(默認是TRUE),目標列NUM_DISTINCT大於設定的Bucket的數量時滿足一定條件會自動轉換成Top-Frequency或Hybrid類型的直方圖。

①Frequency類型直方圖

Frequency類型的直方圖要求直方圖的Bucket數量等於目標列NUM_DISTINCT值的數量,即每一個NUM_DISTINCT對應一個bucket,CBO在選擇執行計劃時只需直接掃描bucket就可很直觀的找到目標值,但是在oracle11g中對於Bucket數量的限制爲254,超過這個值或者NUM_DISTINCT大於收集直方圖時設定的bucket數量就必須用height Balanced類型的直方圖,這就是之前討論的12c之前版本CBO的一個缺陷問題。但是這個問題在12c以後的版本中得到解決,12c新引入了Top-Frequency和Hybrid兩種類型的直方圖。

 SELECT ENDPOINT_NUMBER, ENDPOINT_VALUE
 FROM   USER_HISTOGRAMS
 WHERE  TABLE_NAME='COUNTRIES'
 AND    COLUMN_NAME='COUNTRY_SUBREGION_ID';
 
 ENDPOINT_NUMBER ENDPOINT_VALUE
 --------------- --------------
               1          52792
               6          52793
               8          52794
               9          52795
              10          52796
              12          52797
              14          52798
              23          52799

frequency類型的直方圖bucket和數據的存放方式如下圖所示,由於frequency類型直方圖要求NUM_DISTINCT<Bucket,所以爲一比一存放。當select指定數據時,oracle會首先找到目標數據的ENDPOINT_VALUE和ENDPOINT_NUMBER,然後減去上一個ENDPOINT_NUMBER對應的ENDPOINT_VALUE,可以很精確的計算出分佈不均數據的selectivity,選擇正確的執行計劃。但是由於frequency類型直方圖特殊的要求(NUM_DISTINCT<254)故還存在一下的直方圖類型。

 

通過以下實例也驗證了之前討論的結果,對於直方圖會截取目標列的前32個字符(實際上是15個字符)轉換成一個浮點數(ENDPOINT_ACTUAL_VALUE),在下面例子中Distinct values和number Buckets的值均爲1,CBO默認name列值一樣,產生的執行計劃Cardinality也爲4。

  TEST@PROD1> create table t1(id number,name varchar2(50));
  TEST@PROD1> insert into t1(name) values('0000000000000000000000000000000000000001');
  TEST@PROD1> insert into t1(name) values('0000000000000000000000000000000000000002');
  TEST@PROD1> insert into t1(name) values('0000000000000000000000000000000000000003');
  TEST@PROD1> insert into t1(name) values('0000000000000000000000000000000000000003');
  TEST@PROD1> commit;
  TEST@PROD1> select name,count(name) from t1 group by name;
  
  NAME                                               COUNT(NAME)
  -------------------------------------------------- -----------
  0000000000000000000000000000000000000002                     1
  0000000000000000000000000000000000000003                     2
  0000000000000000000000000000000000000001                     1
  TEST@PROD1> exec dbms_stats.gather_table_stats('test','t1',estimate_percent=>100,method_opt=>'for columns  name size auto');
  TEST@PROD1> select owner,table_name,column_name,num_distinct,density,num_buckets,histogram from dba_tab_col_statistics where table_name='T1';
  
                                 Table           Column                        Distinct          Number
  OWNER                          Name            Name                            Values Density Buckets HISTOGRAM
  ------------------------------ --------------- ------------------------- ------------ ------- ------- ---------------
  TEST                           T1              NAME                                 1       0       1 FREQUENCY
  TEST                           T1              ID                                   0       0       0 NONE
  TEST@PROD1> select * from DBA_TAB_HISTOGRAMS where owner='TEST' and table_name='T1';
  
                                 Table           Column
  OWNER                          Name            Name                      ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT_ACTUAL_VALUE
  ------------------------------ --------------- ------------------------- --------------- -------------- --------------------------------
  TEST                           T1              NAME                                    4     2.5021E+35 00000000000000000000000000000000

發現此時oracle認爲這幾個值是一樣的,在進行查詢的時候結果集爲4。

此時的執行計劃爲:

  TEST@PROD1>  select  * from t1 where name='0000000000000000000000000000000000000003';
  
  Execution Plan
  ----------------------------------------------------------
  Plan hash value: 3617692013
  
  --------------------------------------------------------------------------
  | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  --------------------------------------------------------------------------
  |   0 | SELECT STATEMENT  |      |     4 |   164 |     3   (0)| 00:00:01 |
  |*  1 |  TABLE ACCESS FULL| T1   |     4 |   164 |     3   (0)| 00:00:01 |
  --------------------------------------------------------------------------
  
  Predicate Information (identified by operation id):
  ---------------------------------------------------
  
     1 - filter("NAME"='0000000000000000000000000000000000000003')
  
  
  Statistics
  ----------------------------------------------------------
            0  recursive calls
            0  db block gets
            8  consistent gets
            0  physical reads
            0  redo size
          590  bytes sent via SQL*Net to client
          419  bytes received via SQL*Net from client
            2  SQL*Net roundtrips to/from client
            0  sorts (memory)
            0  sorts (disk)
            2  rows processed

②Height Balanced類型直方圖

在Height Balanced類型的直方圖的selectivity和Cardinality的計算更爲複雜,隨着數據庫版本不同也不同。

1>查詢條件輸入值是popular value:

Cardinality = NUM_ROWS * selectivity

selectivity = (Buckets_this_popular_value / Buckets_total) * Null_Adjust

Null_Adjust = (NUM_ROWS - NUM_NULLS) / NUM_ROWS

 

注:Buckets_this_popular_value:popular value所佔bucket的數量。

Buckets_total:bucket的總數。

2>查詢條件輸入值nonpopular value:

A.oracle version <10.2.0.4(不含10.2.0.4,10.2.0.1)

Cardinality = NUM_ROWS * selectivity

selectivity = OldDensity * Null_Adjust

Null_Adjust = (NUM_ROWS - NUM_NULLS) / NUM_ROWS

OldDensity = SUM(NP.COUNT(i) * NP.COUNT(i)) / ((NUM_ROWS-NUM_NULLS) * SUM(NP.COUNT(i)))

 

注:OldDensity:CBO實際計算可選擇率和結果集的Cardinality。

OldDensity:存儲在DBA_TAB_STATISTICS、DBA_PART_TAB_STATISTICS、DBA_SUBPART_TAB_STATISTICS數據字典中的density字段。

NP.COUNT(i):每個nonpopular value在目標表中的記錄數。

 

B.oracle version = 10.2.0.1

Cardinality = NUM_ROWS * selectivity

selectivity = OldDensity * Null_Adjust

Null_Adjust = (NUM_ROWS - NUM_NULLS) / NUM_ROWS

OldDensity = SUM(DV.COUNT(i) * DV.COUNT(i)) / ((NUM_ROWS-NUM_NULLS) * SUM(DV.COUNT(i)))

 

注:OldDensity:CBO實際計算可選擇率和結果集的Cardinality。

OldDensity:存儲在DBA_TAB_STATISTICS、DBA_PART_TAB_STATISTICS、DBA_SUBPART_TAB_STATISTICS數據字典中的density字段。

DV.COUNT(i):目標列的每個distinct值在目標中的記錄數。

 

C.oracle version >10.2.0.4(含10.2.0.4)

Cardinality = NUM_ROWS * selectivity

selectivity = NewDensity * Null_Adjust

Null_Adjust = (NUM_ROWS - NUM_NULLS) / NUM_ROWS

NewDensity = (Buckets_total - Buckets_all_popular_value) / Buckets_total / (NDV-popular_values.COUNT)

OldDensity = SUM(NP.COUNT(i) * NP.COUNT(i)) / ((NUM_ROWS-NUM_NULLS) * SUM(NP.COUNT(i)))

DNV = NUM_DISTINCT

 

注:NewDensity:CBO實際計算可選擇率和結果集的Cardinality。

OldDensity:存儲在DBA_TAB_STATISTICS、DBA_PART_TAB_STATISTICS、DBA_SUBPART_TAB_STATISTICS數據字典中的density字段。

Buckets_all_popular_value :所有的 popular value所佔用的bucket數量。

Buckets_total:bucket總數。

popular_values.COUNT:popular value的個數。

NP.COUNT(i):每個nonpopular value在目標表中的記錄數。

目標列中的popular_value:簡單的說就是目標列出現頻率較多的值,例如下圖中的52793。nopopular_value:目標列出現頻率低的值,例如下圖中的52792。在Height Balanced類型的直方圖中完全忽略了nopopular_value值,如下圖,將nopopular_value和popular_value存放在一起,由於NUM_DISTINCT>Bucket所以height Balanced類型的直方圖bucket和數據存放形式如下圖所示,oracle會自動將數據隨機存放(即存在一個popular_value值存在不同的bucket中),這樣的存放會影響 CBO對bucket的計算和尋找,影響oracle的性能。

SELECT COUNT(country_subregion_id) AS NUM_OF_ROWS, country_subregion_id 
 FROM   countries 
 GROUP BY country_subregion_id 
 ORDER BY 2;
 
 NUM_OF_ROWS COUNTRY_SUBREGION_ID
 ----------- --------------------
           1                52792
           5                52793
           2                52794
           1                52795
           1                52796
           2                52797
           2                52798
           9                52799

 SELECT ENDPOINT_NUMBER, ENDPOINT_VALUE
 FROM   USER_HISTOGRAMS
 WHERE  TABLE_NAME='COUNTRIES'
 AND    COLUMN_NAME='COUNTRY_SUBREGION_ID';
 
 ENDPOINT_NUMBER ENDPOINT_VALUE
 --------------- --------------
               0          52792
               2          52793
               3          52795
               4          52798
               7          52799

 

通過下面一個例子可以很明顯的看到11g對frequency類型的直方圖bucket的限制是254,若bucket個數超過這個值則直方圖類型自動轉換爲Height Balanced類型。

  TEST@PROD1>  create table t2(id number);--插入一定數據出現列傾斜,並保證distinct爲254
  TEST@PROD1>  select count(distinct id) from t2;

  COUNT(DISTINCTID)
  -----------------
                254
  TEST@PROD1> exec dbms_stats.gather_table_stats('TEST','T2',method_opt=>'for columns  id size auto' ,cascade=>true);
 可以看到當前直方圖distinct爲254,bucket爲254,類型爲FREQUENCY。
  TEST@PROD1>  select owner,table_name,column_name,num_distinct,density,num_buckets,histogram from dba_tab_col_statistics where table_name='T2';
  
                                 Table           Column                        Distinct          Number
  OWNER                          Name            Name                            Values Density Buckets HISTOGRAM
  ------------------------------ --------------- ------------------------- ------------ ------- ------- ---------------
  TEST                           T2              ID                                 254       0     254 FREQUENCY              
  再插入一行:
  TEST@PROD1> insert into t2 values(255);	
  TEST@PROD1> commit;
  TEST@PROD1>  exec dbms_stats.gather_table_stats('TEST','T2',method_opt=>'for columns  id size auto' ,cascade=>true);
  可以看到insert 一行後distinct爲255,大於254後直方圖類型變爲HEIGHT BALANCED。
  TEST@PROD1>  select owner,table_name,column_name,num_distinct,density,num_buckets,histogram from dba_tab_col_statistics where table_name='T2';
  
                                 Table           Column                        Distinct          Number
  OWNER                          Name            Name                            Values Density Buckets HISTOGRAM
  ------------------------------ --------------- ------------------------- ------------ ------- ------- ---------------
  TEST                           T2              ID                                 255       0     254 HEIGHT BALANCED

在oracle12c中引入了Top-Frequency和Hybrid類型的直方圖:

③Top-Frequency類型的直方圖:

Top-Frequency類型的直方圖會忽略統計上無關緊要的nopopular_values值,在oracle12c中用NDV(number for distinct values)代表NUM_DISTINCT,n代表收集直方圖信息時指定的bucket數量(默認是254),p表示能否觸發此類型直方圖的一個閾值,在oracle12c以後的版本中,NUM_DISTINCT>n and ESTIMATE_PERCENT=AUTO_SAMPLE_SIZE and SELECT_NUM>=p時則會產生Top-Frequency類型的直方圖。NUM_DISTINCT>n and ESTIMATE_PERCENT=AUTO_SAMPLE_SIZE and SELECT_NUM < p時產生Hybrid類型的直方圖。

p=(1-(1/n))*100

SELECT_NUM=select_number/totol_number

在oracle12c以後默認ESTIMATE_PERCENT值爲auto,當NUM_DISTINCT>n時只要滿足SELECT_NUM>=p就可以使用Top-Frequency類型的直方圖,即忽略部分無關緊要的nopopular_values使忽略後的NUM_DISTINCT=Bucket,忽略部分nopopular_value值的列實際值和bucket一對一存放。這種存放方式的直方圖就是Top-Frequency直方圖。

 SELECT country_subregion_id, count(*)
 FROM   sh.countries
 GROUP BY country_subregion_id
 ORDER BY 1;
 
 COUNTRY_SUBREGION_ID   COUNT(*)
 -------------------- ----------
                52792          1
                52793          5
                52794          2
                52795          1  --忽略次popular_values
                52796          1
                52797          2
                52798          2
                52799          9

 SELECT ENDPOINT_NUMBER, ENDPOINT_VALUE
 FROM   USER_HISTOGRAMS
 WHERE  TABLE_NAME='COUNTRIES'
 AND    COLUMN_NAME='COUNTRY_SUBREGION_ID';
 
 ENDPOINT_NUMBER ENDPOINT_VALUE
 --------------- --------------
               1          52792
               6          52793
               8          52794
               9          52796
              11          52797
              13          52798
              22          52799

 

④Hybrid類型的直方圖

Hybrid類型的直方圖結合了Frequency和Height Balanced類型直方圖的優點,Height Balanced類型的直方圖允許一個值存在多個bucket中,這對CBO估算成本帶來了誤判,Hybrid利用端點重複計的方式防止了一個值存在多個bucket的可能性。端點重複計就是首先對目標值根據bucket數量進行初始排序,然後oracle優化器會找到桶的邊界然後向前找到不同的值,如此反覆將相同的值存放在同一個bucket中。這就是Hybrid類型的直方圖

oracle中選擇直方圖的順序如下,根據目標列的實際情況會自動選擇不同類型的直方圖(12c or later):

 

 

直方圖收集方法:

①所有索引列自動收集直方圖

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',METHOD_OPT=>'FOR ALL INDEXED COLUMNS SIZE AUTO');

②指定列收集直方圖

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',METHOD_OPT=>'FOR COLUMNS SIZE AUTO EMPLOYEE_ID SALARY');

③指定列收集直方圖,指定Bucket數量。

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',METHOD_OPT=>'FOR COLUMNS EMPLOYEE_ID SIZE 5 SALARY SIZE 10');

④刪除數據字典中某個表指定列上的直方圖信息。

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',METHOD_OPT=>'FOR COLUMNS EMPLOYEE_ID SIZE 1);

⑤刪除數據字典中某個表的直方圖信息

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'HR',TABNAME=>'EMPLOYEES',METHOD_OPT=>'FOR ALL COLUMNS SIZE 1);

 

直方圖對CBO的影響:

①直方圖在shared cursor的影響

對於目標列收集直方圖後,where中的謂詞條件就被認爲是一個“不安全”的謂詞,這裏“不安全”是指施加等值條件之後產生的執行計劃不同,這一點我們能夠理解,本來目標列分佈不均勻纔會收集直方圖信息,那麼不同的謂詞會產生不同執行計劃,相反如果選用主鍵列爲目標列那麼產生的執行計劃一定是一樣的,因爲該列分佈均勻不需要收集直方圖信息。

此處“不安全”的謂詞條件產生的副作用就是:當cursor_sharing值設置爲similar後,本來是想使用綁定變量來替換where後面的具體值,只產生一個執行計劃來提升效率,但是如果該列上有直方圖信息就會導致每一個值都會產生一個child cursor,即硬解析。

通過以下實例可以明顯看到直方圖對cursor的影響,首先先看下不使用直方圖和遊標的結果。

  TEST@PROD1> select id,name,count(id) from ren_test group by id,name;
  
          ID NAME        COUNT(ID)
  ---------- ---------- ----------
           1 A                 100
         100 B                 500
          10 CAP              1000

  TEST@PROD1> exec dbms_stats.gather_table_stats('test','ren_test',method_opt=>'for columns name size 1');
  TEST@PROD1> show parameter cursor_sharing;
  
  NAME                                 TYPE        VALUE
  ------------------------------------ ----------- ------------------------------
  cursor_sharing                       string      EXACT
  TEST@PROD1> select /*+ cursor_sharding_exact_demo */ count(*) from ren_test where name='A';
  
    COUNT(*)
  ----------
         100
  
  TEST@PROD1> select /*+ cursor_sharding_exact_demo */ count(*) from ren_test where name='CAP';
  
    COUNT(*)
  ----------
        1000
  
  TEST@PROD1> select /*+ cursor_sharding_exact_demo */ count(*) from ren_test where name='B';
  
    COUNT(*)
  ----------
         500
  
  TEST@PROD1>  select sql_text,sql_id,version_count from v$sqlarea where sql_text like 'select /*+ cursor_sharding_exact_demo */%';
  
  SQL_TEXT                                                                                             SQL_ID        VERSION_COUNT
  ---------------------------------------------------------------------------------------------------- ------------- -------------
  select /*+ cursor_sharding_exact_demo */ count(*) from ren_test where name='CAP'                     6y23fhj32s5pc             1
  select /*+ cursor_sharding_exact_demo */ count(*) from ren_test where name='B'                       8p8yjgrmvt9mz             1
  select /*+ cursor_sharding_exact_demo */ count(*) from ren_test where name='A'                       49rk5y1xnbsd8             1

通過以上驗證可以看到在不使用直方圖和遊標的情況下三條select產生三個parent cursor,這很容易被理解,CBO認爲這是三條不同的語句,的確在不使用綁定變量的情況下這的確是三條不一樣的SQL,所以在生產中此類型的業務需求需使用綁定變量來降低性能消耗,下面使用cursor來看在有直方圖的前提下的結果。

TEST@PROD1> exec dbms_stats.gather_table_stats('test','ren_test',method_opt=>'for columns name size auto',cascade=>true);
  
  PL/SQL procedure successfully completed.
  
  TEST@PROD1> select owner,table_name,column_name,num_distinct,density,num_buckets,histogram from dba_tab_col_statistics where table_name='REN_TEST';
  
  OWNER      TABLE_NAME                     COLUMN_NAME                    NUM_DISTINCT    DENSITY NUM_BUCKETS HISTOGRAM
  ---------- ------------------------------ ------------------------------ ------------ ---------- ----------- ---------------
  TEST       REN_TEST                       NAME                                      3   .0003125           3 FREQUENCY
  TEST       REN_TEST                       ID                                        3 .333333333           1 NONE
  
  TEST@PROD1> alter system set cursor_sharing='SIMILAR';
  
  System altered.
  
  TEST@PROD1> show parameter cursor_sharing;
  
  NAME                                 TYPE        VALUE
  ------------------------------------ ----------- ------------------------------
  cursor_sharing                       string      SIMILAR
  TEST@PROD1> select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name='A';
  
    COUNT(*)
  ----------
         100
  
  TEST@PROD1> select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name='CAP';
  
    COUNT(*)
  ----------
        1000
  
  TEST@PROD1> select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name='B';
  
    COUNT(*)
  ----------
         500
  TEST@PROD1> select sql_text,sql_id,version_count from v$sqlarea where sql_text like 'select /*+ cursor_sharding_similar_example */%';
  
  SQL_TEXT                                                                                             SQL_ID        VERSION_COUNT
  ---------------------------------------------------------------------------------------------------- ------------- -------------
  select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name=:"SYS_B_0"           9wnd8qp7c931k             3
  
  TEST@PROD1> select sql_text,sql_id,PLAN_HASH_VALUE,CHILD_NUMBER  from v$sql where  sql_text like 'select /*+ cursor_sharding_similar_example */%';
  
  SQL_TEXT                                                                                   SQL_ID        PLAN_HASH_VALUE CHILD_NUMBER
  ------------------------------------------------------------------------------------------ ------------- --------------- ------------
  select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name=:"SYS_B_0" 9wnd8qp7c931k      2524500097            0
  select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name=:"SYS_B_0" 9wnd8qp7c931k      2524500097            1
  select /*+ cursor_sharding_similar_example */ count(*) from ren_test where name=:"SYS_B_0" 9wnd8qp7c931k      2524500097            2

由以上驗證可發現已經使用了綁定變量,但是對應的VERSION_COUNT值爲3,意味着一個parent cursor下有三個child cursor,這三條select全是硬解析,這是一個不正常的現象,正常情況下應只有一個child cursor,原因是name列上有了直方圖信息,直方圖會直接影響shared cursor能否被共享。

 

②直方圖對selectivity的影響

直方圖是oracle爲了解決目標表列數據分佈不均導致CBO選擇執行計劃不準確而引入的新特性,在目標列收集直方圖之後會直接影響CBO估算selectivity和Cardinality,使CBO選擇出貼近實際的執行計劃。

 

使用注意事項:

①只對常用的出現列傾斜的目標列收集直方圖信息,不常用的列不收集直方圖信息。對於分佈均勻的列收集會影響CBO選擇正確的執行計劃。

②根據上述討論,直方圖會影響shared cursor的共享使用,對於使用綁定變量的列不收集直方圖信息。

③oracle自動收集統計信息作業中要注意對直方圖信息的收集。

 

全局統計信息(global statistics)

全局統計信息是oracle從對象本身這一級收集到的統計信息。這一點對分區表十分重要,除非where後限定了只查詢指定分區否則大部分分區表都需要收集全局統計信息。

 

DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'OWNER',TABNAME=>'TABLE_NAME',ESTIMATE_PERCENT=>100,GRANULARITY=>'ALL');

GRANULARITY參數:

GLOBAL(表)

PARTITION(分區表)

SUBPARTITION(子分區)

DEFAULT(10g以後作廢)

GLOBAL AND PARTITION(表+分區)

ALL(表+分區+子分區)

AUTO<默認值>

 

在使用dbms_stats包收集全局統計信息時,如果GRANULARITY參數指定的級別比較低則更低級別的統計信息保持不變,高級別的統計信息時候這一級別的統計信息推導彙總得出,但不能跨級別推導和彙總。若在高級別統計信息之前GLOBAL_STATS=YES,則該級別的統計信息保持不變。

analyze收集全局統計信息時,高級別統計信息由收集的最低級別統計信息逐級推導彙總得到,高級別統計信息推導和彙總原則同dbms_stats一致。

 

注意事項:

①使用BDMS_STATS包對分區表收集和刪除全局統計信息,避免使用analyze收集和刪除全局統計信息。

②收集分區表全局統計信息應使用一致的GRANULARITY參數,若前後使用的參數不同則會導致某級別上統計信息過時不準確。

 

動態採樣(Dynamic Sampling)

上邊討論到CBO存在一個致命的缺陷就是在多表關聯時默認爲where後條件彼此獨立,這種前提在SQL語句中並不總是正確的,這對CBO選擇執行計劃是有致命影響的,會直接影響CBO估算Cardinality。oracle爲了解決這一問題引入了動態採樣技術,對目標列選擇一定比例的採樣結果並放大結果。

動態採樣是oracle對SQL對目標表的數據塊執行操作,通過數據字典獲取總塊數並將執行結果按照這個比例放大采樣結果用於CBO估算真實的Cardinality。oracle默認目標表列數據分佈式均勻狀態,如果分佈式均勻了則可以相對準確的估算出Cardinality。

作用:

①如果使用了動態採樣則不管SQL語句中各列關聯情況怎樣都能比較準確的選擇正確的執行計劃。

②在真實的業務環境中往往會生成一些temporary table,然後頻繁的insert truncate。這種情況下動態採樣顯然比統計信息更適合。

缺陷:

動態採樣僅適用於單表或者多表關聯的驅動表的select、update和delete語句。

開啓動態採樣:

①optimizer_dynamic_sampling參數值大於或等於1(默認開啓)

參數值:

0:不使用動態採樣

1:對沒有統計信息的表採樣(沒有索引,表數據塊數量大於默認採樣數據塊數量)

2:對沒有統計信息的表採樣,使用默認採樣數據塊數量採樣。

3:對沒有統計信息和CBO無法通過統計信息準確評估Cardinality的表採樣,使用默認採樣數據塊數量採樣。

4:在3基礎上加上對單表查詢條件至少出現兩列的表啓用動態採樣,使用默認採樣數據塊數量採樣。

5:同4條件,使用2倍默認採樣數據塊數量採樣。

6:同5條件,使用4倍默認採樣數據塊數量採樣。

7:同6條件,使用8倍默認採樣數據塊數量採樣。

8:同7條件,使用32倍默認採樣數據塊數量採樣。

9:同8條件,使用128倍默認採樣數據塊數量採樣。

10:同9條件,使用所有目標表的數據塊數量採樣。

②使用hint:dynamic_sampling(t.level)

參數值:

0:不啓用動態採樣。

1:啓用動態採樣,使用默認採樣數據塊數量。

2:啓用動態採樣,使用2倍默認採樣數據塊數量。

3:啓用動態採樣,使用4倍默認採樣數據塊數量。

4:啓用動態採樣,使用8倍默認採樣數據塊數量。

5:啓用動態採樣,使用16倍默認採樣數據塊數量。

6:啓用動態採樣,使用32倍默認採樣數據塊數量。

7:啓用動態採樣,使用64倍默認採樣數據塊數量。

8:啓用動態採樣,使用128倍默認採樣數據塊數量。

9:啓用動態採樣,使用256倍默認採樣數據塊數量。

10:啓用動態採樣,使用所有目標表的數據塊數量。

 

多列統計信息(MultiColumn Statistics)

上邊討論了動態採樣,實際上在11g中還引入了多列統計信息來解決有關聯關係的幾個列統計信息收集的問題。

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'OWNER',TABNAME=>'TABLE_NAME',METHOD_OPT=>'FOR COLUMNS(COLUMN1,COLUMN2) SIZE AUTO',ESTIMATE_PERCENT=>100);

 

系統統計信息(System statistics)

系統統計信息是oracle對所在服務器的cpu主頻、單塊讀平均耗時、多塊讀平均耗時和單次多塊讀所能讀取數據塊的平均值等信息收集的信息彙總。

收集系統統計信息:(順序執行)

①DBMS_STATS.GATHER_SYSTEM_STATS('start');

②正常壓力使用數據庫服務器。

③DBMS_STATS.GATHER_SYSTEM_STATS('stop');

 

作用:

引入了CPU cost model,在實際成本計算時計算CPU cost和I/O cost作爲目標SQL的cost。

對全表掃描的影響:

①oracle 9i以前雖已引入系統統計信息但在AUX_STATS$裏沒有系統統計信息的記錄,即全表掃描在默認情況下(_OPTIMIZER_COST_MODEL=CHOOSE)全部是IO cost。

TSC cost =1 + BLOCKS/MBDivisor

=1 + BLOCKS /(1.6765 * power(db_file_multiblock_read_count,0.6581))

②oracle 10g以後的版本在AUX_STATS$裏已經有了默認值,全表掃描的cost和DB_FILE_MULTIBLOCK_READ_COUNT參數值就沒有關係了。

③oracle 11g中禁用了CPU Cost Model採用就的IO Cost Model,依然和DB_FILE_MULTIBLOCK_READ_COUNT參數值沒有關係。

TSC cost =1 + BLOCKS/MBDivisor

=1 + BLOCKS /(1.6765 * power(_db_file_optimizer_read_count,0.6581))

④如果系統統計信息被收集就意味着CPU Cost Model倍完全開啓。

TSC cost =I/O Cost + CPU Cost

I/O Cost = 1 + CELL((BLOCK / MBRC) * (MREADTIM/SREADTIM))

CPU Cost = ROUND(#CPUCYCLES/CPUSPEED/1000/SREADTIM)

 

數據字典統計信息()

數據字典統計信息是對數據字典基表(TAB$,IND$)、數據字典基表上索引以及數據字典基表列的信息信息描述。當一條SQL執行的時候首先從TAB$獲取目標表的統計信息,同時還需要獲取TAB$表的統計信息來快速從TAB$表中找到目標表的統計信息,以便讓CBO產生正確的執行計劃。

EXEC DBMS_STATS.GAHTER_DICTIONARY_STATS(OWNNAME=>'sys',TABNAME=>'name$',estimate_percent=>100,cascade=>true);

EXEC DBMS_STATS.DELETE_DICTIONARY_STATS(OWNNAME=>'sys',TABNAME=>'name$');

 

內部對象統計信息

用來描述oracle一些內部表(X$系列表)的相關信息。X$系列表是oracle的存儲與實例相關各種信息的自定義內存結構。

在數據庫中實際上是需要頻繁的訪問內部表的。X$系列表雖然是內存結構但是實際上其統計信息是需要存放在數據字典中持久化存放的,也就意味着重啓數據庫之後不需要重新收集內部統計信息,而且很少情況下需要手工收集內部統計信息,默認是自動定期收集。

①一次性收集所有X$表的內部對象信息:

EXEC DBMS_STATS.GATHER_FIXED_OBJECTS_STATS();

②一次性刪除所有X$表的內部對象信息:

EXEC DBMS_STATS.DELETE_FIXED_OBJECTS_STATS();

③收集指定X$內部表的對象信息:

EXEC DBMS_STATS.GATHER_FIXED_OBJECTS_STATS(OWNNAME=>'sys',TABNAME=>'X$name',estimate_percent=>100,cascade=>true);

④刪除指定X$內部表的對象信息:

EXEC DBMS_STATS.DELETE_FIXED_OBJECTS_STATS(OWNNAME=>'sys',TABNAME=>'X$name');

從以下示例可以看到簡單的rman也會調用大量的X$表,由此可見若此類表沒有統計信息或者統計信息不具有代表性會是一件很糟糕的事情,會在shared pool中產生大量的latch,影響性能。

SYS@PROD1> select count(*) from v$rman_status;


Execution Plan
----------------------------------------------------------
Plan hash value: 3278739756

--------------------------------------------------------------------------------------
| Id  | Operation                | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |           |     1 |   104 |     7 (100)| 00:00:01 |
|   1 |  SORT AGGREGATE          |           |     1 |   104 |            |          |
|*  2 |   HASH JOIN              |           |     1 |   104 |     7 (100)| 00:00:01 |
|*  3 |    HASH JOIN             |           |     1 |    78 |     4 (100)| 00:00:01 |
|*  4 |     HASH JOIN OUTER      |           |   100 |  5200 |     1 (100)| 00:00:01 |
|   5 |      FIXED TABLE FULL    | X$KCCRSR  |   100 |  2600 |     0   (0)| 00:00:01 |
|   6 |      FIXED TABLE FULL    | X$KRBMRST |   100 |  2600 |     0   (0)| 00:00:01 |
|   7 |     VIEW                 |           |   100 |  2600 |     3 (100)| 00:00:01 |
|   8 |      HASH GROUP BY       |           |   100 |  9100 |     3 (100)| 00:00:01 |
|*  9 |       HASH JOIN OUTER    |           |   100 |  9100 |     2 (100)| 00:00:01 |
|  10 |        FIXED TABLE FULL  | X$KCCRSR  |   100 |  2600 |     0   (0)| 00:00:01 |
|  11 |        VIEW              |           |   100 |  6500 |     1 (100)| 00:00:01 |
|  12 |         HASH GROUP BY    |           |   100 |  6500 |     1 (100)| 00:00:01 |
|  13 |          FIXED TABLE FULL| X$KSFQP   |   100 |  6500 |     0   (0)| 00:00:01 |
|  14 |    VIEW                  |           |   100 |  2600 |     3 (100)| 00:00:01 |
|  15 |     HASH UNIQUE          |           |   100 |  6200 |     3 (100)| 00:00:01 |
|* 16 |      HASH JOIN OUTER     |           |   100 |  6200 |     2 (100)| 00:00:01 |
|  17 |       FIXED TABLE FULL   | X$KCCRSR  |   100 |  2600 |     0   (0)| 00:00:01 |
|  18 |       VIEW               |           |     1 |    36 |     1 (100)| 00:00:01 |
|  19 |        WINDOW SORT       |           |     1 |    49 |     1 (100)| 00:00:01 |
|* 20 |         FIXED TABLE FULL | X$KSFQP   |     1 |    49 |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

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

   2 - access("ODEV"."RECID"=NVL("ID_KRBMRST","R2"."RSRRID") AND
              "ODEV"."STAMP"=NVL("STAMP_KRBMRST","R2"."RSRTST"))
   3 - access("HH"."RECID"=NVL("ID_KRBMRST","R2"."RSRRID") AND
              "HH"."STAMP"=NVL("STAMP_KRBMRST","R2"."RSRTST"))
   4 - access("R2"."RSRRID"="ID_KRBMRST"(+) AND
              "R2"."RSRTST"="STAMP_KRBMRST"(+))
   9 - access("R"."RSRRID"="RS"."RMAN_STATUS_RECID"(+) AND
              "R"."RSRTST"="RS"."RMAN_STATUS_STAMP"(+))
  16 - access("R"."RSRRID"="RS"."RMAN_STATUS_RECID"(+) AND
              "R"."RSRTST"="RS"."RMAN_STATUS_STAMP"(+))
  20 - filter("TYPE"=2)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        422  bytes sent via SQL*Net to client
        420  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

內部對象統計信息收集策略注意事項:

①X$表的數據會隨着oracle的負載變化而變化,可能之前的統計信息不具有代表性。

②DBMS_STATS.GATHER_FIXED_OBJECTS_STATS收集統計信息會產生相當大的負載,故應選擇系統負載不是很大的時候收集內部對象統計信息。但數據庫不能太閒,否則不具有代表性。

③由於X$表爲內存結構,故rac環境需要每個節點都要收集內部對象統計信息。

④若因爲某個表的統計信息不準確導致某些操作hang住可通過dbms_stats包對某個表單獨收集統計信息。

 

收集統計信息原則和建議:

原則:

建議使用自動作業來收集數據庫的統計信息,對於數據量很大的OLAP 和DSS類型的系統建議通過shell腳本自定義收集。

建議:

①數據量變化較大時儘快收集統計信息。

②生產系統遷移後收集統計信息。

③對包含日期的表收集統計信息,防止謂詞越界。

④收集統計信息時動態採樣比例根據實際情況而定,10g建議爲30%,11g以上版本建議使用dbms_stats.auto_sample_size(全新hash算法)。

⑤建議使用dbms_stats收集統計信息。

⑥對於內部對象統計信息和系統統計信息建議一次性收集,若沒有診斷的性能問題不需要二次收集。

 

--End

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