浅析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

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