我们通常使用下面脚本收集表和索引的统计信息。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'TAB_OWNER'
    3. tabname => 'TAB_NAME'
    4. estimate_percent => 根据表大小设置,
    5. method_opt => 'for all columns size repeat'
    6. no_invalidate => FALSE
    7. degree => 根据表大小,CPU 资源和负载设置,
    8. granularity => 'AUTO'
    9. cascade => TRUE);
    10. END;

    ownname 表示表的拥有者,不区分大小写。

    tabname 表示表名字,不区分大小写。

    granularity 表示收集统计信息的粒度,该选项只对分区表生效,默认为 AUTO,表示让 Oracle 根据表的分区类型自己判断如何收集分区表的统计信息。对于该选项,我们一般采用 AUTO 方式,也就是数据库默认方式,因此,在后面的脚本中,省略该选项。

    estimate_percent 表示采样率,范围是 0.000 001~100。

    我们一般对小于 1GB 的表进行 100% 采样,因为表很小,即使 100% 采样速度也比较快。有时候小表有可能数据分布不均衡,如果没有 100% 采样,可能会导致统计信息不准。因此我们建议对小表 100% 采样。

    我们一般对表大小在 1GB~5GB 的表采样 50%对大于 5GB 的表采样 30%。如果表特别大,有几十甚至上百 GB,我们建议应该先对表进行分区,然后分别对每个分区收集统计信息。

    一般情况下,为了确保统计信息比较准确,我们建议采样率不要低于 30%。

    我们可以使用下面脚本查看表的采样率。

    1. SELECT owner,
    2. table_name,
    3. num_rows,
    4. sample_size,
    5. round(sample_size / num_rows * 100) estimate_percent
    6. FROM DBA_TAB_STATISTICS
    7. WHERE owner='SCOTT' AND table_name='T_STATS';
    1. OWNER TABLE_NAME NUM_ROWS SAMPLE_SIZE ESTIMATE_PERCENT
    2. --------------- --------------- ---------- ----------- ----------------
    3. SCOTT T_STATS 72674 72674 100

    从上面查询我们可以看到,对表 T_STATS 是 100% 采样的。现在我们将采样率设置为 30%。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 30,
    5. method_opt => 'for all columns size auto',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.
    1. SELECT owner,
    2. table_name,
    3. num_rows,
    4. sample_size,
    5. round(sample_size / num_rows * 100) estimate_percent
    6. FROM DBA_TAB_STATISTICS
    7. WHERE owner='SCOTT' AND table_name='T_STATS';
    1. OWNER TABLE_NAME NUM_ROWS SAMPLE_SIZE ESTIMATE_PERCENT
    2. --------------- --------------- ---------- ----------- ----------------
    3. SCOTT T_STATS 73067 21920 30

    从上面查询我们可以看到采样率为 30%,表的总行数被估算为 73 067,而实际上表的总行数为 72 674。设置采样率 30% 的时候,一共分析了 21 920 条数据,表的总行数等于 round(21 920*100/30),也就是 73 067。

    除非一个表是小表,否则没有必要对一个表 100% 采样。因为表一直都会进行 DML 操作,表中的数据始终是变化的。

    method_opt 用于控制收集直方图策略。

    method_opt => 'for all columns size 1'表示所有列都不收集直方图,如下所示。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for all columns size 1',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    我们查看直方图信息。

    1. select a.column_name,
    2. b.num_rows,
    3. a.num_nulls,
    4. a.num_distinct Cardinality,
    5. round(a.num_distinct / b.num_rows * 100, 2) selectivity,
    6. a.histogram,
    7. a.num_buckets
    8. from dba_tab_col_statistics a, dba_tables b
    9. where a.owner = b.owner
    10. and a.table_name = b.table_name
    11. and a.owner = 'SCOTT'
    12. and a.table_name = 'T_STATS';
    1. COLUMN_NAME NUM_ROWS NUM_NULLS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS
    2. --------------- ---------- ---------- ----------- ----------- --------------- -------
    3. EDITION_NAME 72674 72674 0 0 NONE 0
    4. NAMESPACE 72674 1 21 .03 NONE 1
    5. SECONDARY 72674 0 2 0 NONE 1
    6. GENERATED 72674 0 2 0 NONE 1
    7. TEMPORARY 72674 0 2 0 NONE 1
    8. STATUS 72674 0 2 0 NONE 1
    9. TIMESTAMP 72674 1 1592 2.19 NONE 1
    10. LAST_DDL_TIME 72674 1 1521 2.09 NONE 1
    11. CREATED 72674 0 1472 2.03 NONE 1
    12. OBJECT_TYPE 72674 0 45 .06 NONE 1
    13. DATA_OBJECT_ID 72674 64833 7796 10.73 NONE 1
    14. OBJECT_ID 72674 1 72673 100 NONE 1
    15. SUBOBJECT_NAME 72674 72145 140 .19 NONE 1
    16. OBJECT_NAME 72674 0 44333 61 NONE 1
    17. OWNER 72674 0 31 .04 NONE 1
    18. 15 rows selected.

    从上面查询我们看到,所有列都没有收集直方图。

    method_opt => 'for all columns size skewonly'表示对表中所有列收集自动判断是否收集直方图,如下所示。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for all columns size skewonly',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    我们查看直方图信息,如下所示。

    1. select a.column_name,
    2. b.num_rows,
    3. a.num_nulls,
    4. a.num_distinct Cardinality,
    5. round(a.num_distinct / b.num_rows * 100, 2) selectivity,
    6. a.histogram,
    7. a.num_buckets
    8. from dba_tab_col_statistics a, dba_tables b
    9. where a.owner = b.owner
    10. and a.table_name = b.table_name
    11. and a.owner = 'SCOTT'
    12. and a.table_name = 'T_STATS';
    1. COLUMN_NAME NUM_ROWS NUM_NULLS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS
    2. --------------- ---------- ---------- ----------- ----------- --------------- -------
    3. EDITION_NAME 72674 72674 0 0 NONE 0
    4. NAMESPACE 72674 1 21 .03 FREQUENCY 21
    5. SECONDARY 72674 0 2 0 FREQUENCY 2
    6. GENERATED 72674 0 2 0 FREQUENCY 2
    7. TEMPORARY 72674 0 2 0 FREQUENCY 2
    8. STATUS 72674 0 2 0 FREQUENCY 2
    9. TIMESTAMP 72674 1 1592 2.19 HEIGHT BALANCED 254
    10. LAST_DDL_TIME 72674 1 1521 2.09 HEIGHT BALANCED 254
    11. CREATED 72674 0 1472 2.03 HEIGHT BALANCED 254
    12. OBJECT_TYPE 72674 0 45 .06 FREQUENCY 45
    13. DATA_OBJECT_ID 72674 64833 7796 10.73 HEIGHT BALANCED 254
    14. OBJECT_ID 72674 1 72673 100 NONE 1
    15. SUBOBJECT_NAME 72674 72145 140 .19 FREQUENCY 140
    16. OBJECT_NAME 72674 0 44333 61 HEIGHT BALANCED 254
    17. OWNER 72674 0 31 .04 FREQUENCY 31
    18. 15 rows selected.

    从上面查询我们可以看到,除了 OBJECT_ID 列和 EDITION_NAME 列,其余所有列都收集了直方图。因为 EDITION_NAME 列全是 NULL,所以没必要收集直方图。OBJECT_ID 列选择性为 100%,没必要收集直方图。

    在实际工作中千万不要使用**method_opt => 'for all columns size skewonly'** 收集直方图信息,因为并不是表中所有的列都会出现在 where 条件中,对没有出现在 where 条件中的列收集直方图没有意义。

    method_opt => 'for all columns size auto'表示对出现在 where 条件中的列自动判断是否收集直方图。

    现在我们删除表中所有列的直方图。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for all columns size 1',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    我们执行下面 SQL,以便将 owner 列放入 where 条件中。

    1. select count(*) from t_stats where owner='SYS';
    1. COUNT(*)
    2. ----------
    3. 30850

    接下来我们刷新数据库监控信息。

    1. begin
    2. dbms_stats.flush_database_monitoring_info;
    3. end;
    1. PL/SQL procedure successfully completed.

    我们使用method_opt => 'for all columns size auto'方式对表收集统计信息。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for all columns size auto',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    然后我们查看直方图信息。

    1. select a.column_name,
    2. b.num_rows,
    3. a.num_nulls,
    4. a.num_distinct Cardinality,
    5. round(a.num_distinct / b.num_rows * 100, 2) selectivity,
    6. a.histogram,
    7. a.num_buckets
    8. from dba_tab_col_statistics a, dba_tables b
    9. where a.owner = b.owner
    10. and a.table_name = b.table_name
    11. and a.owner = 'SCOTT'
    12. and a.table_name = 'T_STATS';
    1. COLUMN_NAME NUM_ROWS NUM_NULLS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS
    2. --------------- ---------- ---------- ----------- ----------- --------------- -------
    3. EDITION_NAME 72674 72674 0 0 NONE 0
    4. NAMESPACE 72674 1 21 .03 NONE 1
    5. SECONDARY 72674 0 2 0 NONE 1
    6. GENERATED 72674 0 2 0 NONE 1
    7. TEMPORARY 72674 0 2 0 NONE 1
    8. STATUS 72674 0 2 0 NONE 1
    9. TIMESTAMP 72674 1 1592 2.19 NONE 1
    10. LAST_DDL_TIME 72674 1 1521 2.09 NONE 1
    11. CREATED 72674 0 1472 2.03 NONE 1
    12. OBJECT_TYPE 72674 0 45 .06 NONE 1
    13. DATA_OBJECT_ID 72674 64833 7796 10.73 NONE 1
    14. OBJECT_ID 72674 1 72673 100 NONE 1
    15. SUBOBJECT_NAME 72674 72145 140 .19 NONE 1
    16. OBJECT_NAME 72674 0 44333 61 NONE 1
    17. OWNER 72674 0 31 .04 FREQUENCY 31
    18. 15 rows selected.

    从上面查询我们可以看到,Oracle 自动地对 owner 列收集了直方图。

    思考,如果将选择性比较高的列放入 where 条件中,会不会自动收集直方图?现在我们将 OBJECT_NAME 列放入 where 条件中。

    1. select count(*) from t_stats where object_name='EMP';
    1. COUNT(*)
    2. ----------
    3. 3

    然后我们刷新数据库监控信息。

    1. begin
    2. dbms_stats.flush_database_monitoring_info;
    3. end;
    1. PL/SQL procedure successfully completed.

    我们收集统计信息。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for all columns size auto',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    我们查看 OBJECT_NAME 列是否收集了直方图。

    1. select a.column_name,
    2. b.num_rows,
    3. a.num_nulls,
    4. a.num_distinct Cardinality,
    5. round(a.num_distinct / b.num_rows * 100, 2) selectivity,
    6. a.histogram,
    7. a.num_buckets
    8. from dba_tab_col_statistics a, dba_tables b
    9. where a.owner = b.owner
    10. and a.table_name = b.table_name
    11. and a.owner = 'SCOTT'
    12. and a.table_name = 'T_STATS';
    1. COLUMN_NAME NUM_ROWS NUM_NULLS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS
    2. --------------- ---------- ---------- ----------- ----------- --------------- -------
    3. EDITION_NAME 72674 72674 0 0 NONE 0
    4. NAMESPACE 72674 1 21 .03 NONE 1
    5. SECONDARY 72674 0 2 0 NONE 1
    6. GENERATED 72674 0 2 0 NONE 1
    7. TEMPORARY 72674 0 2 0 NONE 1
    8. STATUS 72674 0 2 0 NONE 1
    9. TIMESTAMP 72674 1 1592 2.19 NONE 1
    10. LAST_DDL_TIME 72674 1 1521 2.09 NONE 1
    11. CREATED 72674 0 1472 2.03 NONE 1
    12. OBJECT_TYPE 72674 0 45 .06 NONE 1
    13. DATA_OBJECT_ID 72674 64833 7796 10.73 NONE 1
    14. OBJECT_ID 72674 1 72673 100 NONE 1
    15. SUBOBJECT_NAME 72674 72145 140 .19 NONE 1
    16. OBJECT_NAME 72674 0 44333 61 NONE 1
    17. OWNER 72674 0 31 .04 FREQUENCY 31
    18. 15 rows selected.

    从上面查询我们可以看到,OBJECT_NAME 列没有收集直方图。由此可见,使用 AUTO 方式收集直方图很智能。mothod_opt 默认的参数就是 for all columns size auto。

    method_opt => 'for all columns size repeat'表示当前有哪些列收集了直方图,现在就对哪些列收集直方图。

    当前只对 OWNER 列收集了直方图,现在我们使用 REPEAT 方式收集直方图。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for all columns size repeat',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    我们查看直方图信息。

    1. select a.column_name,
    2. b.num_rows,
    3. a.num_nulls,
    4. a.num_distinct Cardinality,
    5. round(a.num_distinct / b.num_rows * 100, 2) selectivity,
    6. a.histogram,
    7. a.num_buckets
    8. from dba_tab_col_statistics a, dba_tables b
    9. where a.owner = b.owner
    10. and a.table_name = b.table_name
    11. and a.owner = 'SCOTT'
    12. and a.table_name = 'T_STATS';
    1. COLUMN_NAME NUM_ROWS NUM_NULLS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS
    2. --------------- ---------- ---------- ----------- ----------- --------------- -------
    3. EDITION_NAME 72674 72674 0 0 NONE 0
    4. NAMESPACE 72674 1 21 .03 NONE 1
    5. SECONDARY 72674 0 2 0 NONE 1
    6. GENERATED 72674 0 2 0 NONE 1
    7. TEMPORARY 72674 0 2 0 NONE 1
    8. STATUS 72674 0 2 0 NONE 1
    9. TIMESTAMP 72674 1 1592 2.19 NONE 1
    10. LAST_DDL_TIME 72674 1 1521 2.09 NONE 1
    11. CREATED 72674 0 1472 2.03 NONE 1
    12. OBJECT_TYPE 72674 0 45 .06 NONE 1
    13. DATA_OBJECT_ID 72674 64833 7796 10.73 NONE 1
    14. OBJECT_ID 72674 1 72673 100 NONE 1
    15. SUBOBJECT_NAME 72674 72145 140 .19 NONE 1
    16. OBJECT_NAME 72674 0 44333 61 NONE 1
    17. OWNER 72674 0 31 .04 FREQUENCY 31
    18. 15 rows selected.

    从查询中我们可以看到,使用 REPEAT 方式延续了上次收集直方图的策略。对一个运行稳定的系统,我们应该采用 REPEAT 方式收集直方图。

    单独对某一列收集直方图:method_opt => 'for columns 列名 size skewonly'

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T',
    4. estimate_percent => 100,
    5. method_opt => 'for columns SYS_STUNA$6DVXJXTP05EH56DTIR0X size skewonly',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;

    method_opt => 'for columns object_type size skewonly'表示单独对 OBJECT_TYPE 列收集直方图,对于其余列,如果之前收集过直方图,现在也收集直方图。

    1. BEGIN
    2. DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
    3. tabname => 'T_STATS',
    4. estimate_percent => 100,
    5. method_opt => 'for columns object_type size skewonly',
    6. no_invalidate => FALSE,
    7. degree => 1,
    8. cascade => TRUE);
    9. END;
    1. PL/SQL procedure successfully completed.

    我们查看直方图信息。

    1. select a.column_name,
    2. b.num_rows,
    3. a.num_nulls,
    4. a.num_distinct Cardinality,
    5. round(a.num_distinct / b.num_rows * 100, 2) selectivity,
    6. a.histogram,
    7. a.num_buckets
    8. from dba_tab_col_statistics a, dba_tables b
    9. where a.owner = b.owner
    10. and a.table_name = b.table_name
    11. and a.owner = 'SCOTT'
    12. and a.table_name = 'T_STATS';
    1. COLUMN_NAME NUM_ROWS NUM_NULLS CARDINALITY SELECTIVITY HISTOGRAM NUM_BUCKETS
    2. --------------- ---------- ---------- ----------- ----------- --------------- -------
    3. EDITION_NAME 72674 72674 0 0 NONE 0
    4. NAMESPACE 72674 1 21 .03 NONE 1
    5. SECONDARY 72674 0 2 0 NONE 1
    6. GENERATED 72674 0 2 0 NONE 1
    7. TEMPORARY 72674 0 2 0 NONE 1
    8. STATUS 72674 0 2 0 NONE 1
    9. TIMESTAMP 72674 1 1592 2.19 NONE 1
    10. LAST_DDL_TIME 72674 1 1521 2.09 NONE 1
    11. CREATED 72674 0 1472 2.03 NONE 1
    12. OBJECT_TYPE 72674 0 45 .06 FREQUENCY 45
    13. DATA_OBJECT_ID 72674 64833 7796 10.73 NONE 1
    14. OBJECT_ID 72674 1 72673 100 NONE 1
    15. SUBOBJECT_NAME 72674 72145 140 .19 NONE 1
    16. OBJECT_NAME 72674 0 44333 61 NONE 1
    17. OWNER 72674 0 31 .04 FREQUENCY 31
    18. 15 rows selected.

    从查询中我们可以看到,OBJECT_TYPE 列收集了直方图,因为之前收集过 owner 列直方图,现在也跟着收集了 owner 列的直方图。

    在实际工作中,我们需要对列收集直方图就收集直方图,需要删除某列直方图就删除其直方图,当系统趋于稳定之后,使用 REPEAT 方式收集直方图。

    no_invalidate 表示共享池中涉及到该表的游标是否立即失效,默认值为 DBMS_STATS. AUTO_INVALIDATE,表示让 Oracle 自己决定是否立即失效。我们建议将 no_invalidate 参数设置为 FALSE,立即失效。因为我们发现有时候 SQL 执行缓慢是因为统计信息过期导致,重新收集了统计信息之后执行计划还是没有更改,原因就在于没有将这个参数设置为 false。

    degree 表示收集统计信息的并行度,默认为 NULL。如果表没有设置 degree,收集统计信息的时候后就不开并行;如果表设置了 degree,收集统计信息的时候就按照表的 degree 来开并行。可以查询 DBA_TABLES.degree 来查看表的 degree,一般情况下,表的 degree 都为 1。我们建议可以根据当时系统的负载、系统中 CPU 的个数以及表大小来综合判断设置并行度。

    1. SELECT DEGREE FROM DBA_TABLES WHERE owner = 'SCOTT' AND table_name = 'T_STATS';

    Screen Shot 2021-11-03 at 9.24.39 AM.png

    cascade 表示在收集表的统计信息的时候,是否级联收集索引的统计信息,默认值为 DBMS_STATS.AUTO_CASCADE,表示让 Oracle 自己判断是否级联收集索引的统计信息。我们一般将其设置为 TRUE,在收集表的统计信息的时候,级联收集索引的统计信息。