Oracle优化07-分析及动态采样-DBMS_STATS 包

性能数据的收集包含这样几个存储过程:

  • GATHER_DATABASE_STATS Procedures
  • GATHER_DICTIONARY_STATS Procedure
  • GATHER_FIXED_OBJECTS_STATS Procedure
  • GATHER_INDEX_STATS Procedure
  • GATHER_SCHEMA_STATS Procedures
  • GATHER_SYSTEM_STATS Procedures
  • GATHER_TABLE_STATS Procedure

从名字也可以看出各自的作用,这些存储过程用来收集数据库不同级别对象的性能数据,包括:数据库,数据字典,表,索引,SCHEMA 的性能等。


GATHER_TABLE_STATS 收集信息功能

我们分析时最常用到的就是GATHER_TABLE_STATS 了。

在11gR2版本中dbms_stats包下的 gather_table_stats存过定义如下

我们可以在注释中看到 This procedure gathers table and column (and index) statistics.

procedure gather_table_stats
    (ownname varchar2, tabname varchar2, partname varchar2 default null,
     estimate_percent number default DEFAULT_ESTIMATE_PERCENT,
     block_sample boolean default FALSE,
     method_opt varchar2 default DEFAULT_METHOD_OPT,
     degree number default to_degree_type(get_param('DEGREE')),
     granularity varchar2 default  DEFAULT_GRANULARITY,
     cascade boolean default DEFAULT_CASCADE,
     stattab varchar2 default null, statid varchar2 default null,
     statown varchar2 default null,
     no_invalidate boolean default
       to_no_invalidate_type(get_param('NO_INVALIDATE')),
     stattype varchar2 default 'DATA',
     force boolean default FALSE,
     -- the context is intended for internal use only.
     context dbms_stats.CContext default null);

--
-- This procedure gathers table and column (and index) statistics.
-- It attempts to parallelize as much of the work as possible, but there
-- are some restrictions as described in the individual parameters.
-- This operation will not parallelize if the user does not have select
-- privilege on the table being analyzed.
--
-- Input arguments:
--   ownname - schema of table to analyze
--   tabname - name of table
--   partname - name of partition
--   estimate_percent - Percentage of rows to estimate (NULL means compute).
--      The valid range is [0.000001,100].  Use the constant
--      DBMS_STATS.AUTO_SAMPLE_SIZE to have Oracle determine the
--      appropriate sample size for good statistics. This is the default.
--      The default value can be changed using set_param procedure.
--   block_sample - whether or not to use random block sampling instead of
--      random row sampling.  Random block sampling is more efficient, but
--      if the data is not randomly distributed on disk then the sample values
--      may be somewhat correlated.  Only pertinent when doing an estimate
--      statistics.
--   method_opt - method options of the following format
--
--         method_opt  := FOR ALL [INDEXED | HIDDEN] COLUMNS [size_clause]
--                        FOR COLUMNS [size_clause]
--                        column|attribute [size_clause]
--                        [,column|attribute [size_clause] ... ]
--
--         size_clause := SIZE [integer | auto | skewonly | repeat],
--                        where integer is between 1 and 254
--
--         column      := column name | extension name | extension
--
--      default is FOR ALL COLUMNS SIZE AUTO.
--      The default value can be changed using set_param procedure.
--      Optimizer related table statistics are always gathered.
--
--      If an extension is provided, the procedure create the extension if it
--      does not exist already. Please refer to create_extended_stats for
--      description of extension.
--
--   degree - degree of parallelism (NULL means use of table default value
--      which was specified by DEGREE clause in CREATE/ALTER TABLE statement)
--      Use the constant DBMS_STATS.DEFAULT_DEGREE for the default value
--      based on the initialization parameters.
--      default for degree is NULL.
--      The default value can be changed using set_param procedure.
--   granularity - the granularity of statistics to collect (only pertinent
--      if the table is partitioned)
--     'AUTO' - the procedure determines what level of statistics to collect
--     'GLOBAL AND PARTITION' - gather global- and partition-level statistics
--     'APPROX_GLOBAL AND PARTITION' - This option is similar to
--        'GLOBAL AND PARTITION'. But the global statistics are aggregated
--         from partition level statistics. It will aggregate all statistics except number of
--         distinct values for columns and number of distinct keys of indexes.
--         The existing histograms of the columns at the table level
--         are also aggregated.The global statistics are  gathered
--         (i.e., going back to GLOBAL AND PARTITION behaviour)
--         if partname argument is null. The aggregation will use only
--         partitions with statistics, so to get accurate global statistics,
--         user has to make sure to have statistics for all partitions.
--
--
--         This option is useful when you collect statistics for a new partition added
--         into a range partitioned table (for example, a table  partitioned by month).
--         The new data in the partition makes the global statistics stale (especially
--         the min/max values of the partitioning column). This stale global statistics
--         may cause suboptimal plans.  In this scenario, users can collect statistics
--         for the newly added partition with 'APPROX_GLOBAL AND PARTITION'
--         option so that the global statistics will reflect the newly added range.
--         This option will take less time than 'GLOBAL AND PARTITION' option since the
--         global statistics are aggregated from underlying partition level statistics.
--         Note that, if you are using APPROX_GLOBAL AND PARTITION,
--         you still  need to collect global statistics (with granularity = 'GLOBAL' option)
--         when there is substantial amount of change at the table level.
--         For example you added 10% more data to the table.  This is needed to get the
--         correct number of distinct values/keys statistic at table level.
--     'SUBPARTITION' - gather subpartition-level statistics
--     'PARTITION' - gather partition-level statistics
--     'GLOBAL' - gather global statistics
--     'ALL' - gather all (subpartition, partition, and global) statistics
--     default for granularity is AUTO.
--     The default value can be changed using set_param procedure.
--   cascade - gather statistics on the indexes for this table.
--      Use the constant DBMS_STATS.AUTO_CASCADE to have Oracle determine
--      whether index stats to be collected or not. This is the default.
--      The default value can be changed using set_param procedure.
--      Using this option is equivalent to running the gather_index_stats
--      procedure on each of the table's indexes.
--   stattab - The user stat table identifier describing where to save
--      the current statistics.
--   statid - The (optional) identifier to associate with these statistics
--      within stattab.
--   statown - The schema containing stattab (if different then ownname)
--   no_invalidate - Do not invalide the dependent cursors if set to TRUE.
--     The procedure invalidates the dependent cursors immediately
--     if set to FALSE.
--     Use DBMS_STATS.AUTO_INVALIDATE to have oracle decide when to
--     invalidate dependend cursors. This is the default. The default
--     can be changed using set_param procedure.
--     When the 'cascade' argument is specified, not pertinent with certain
--     types of indexes described in the gather_index_stats section.
--   force - gather statistics of table even if it is locked.
--   context - internal use only.
--
-- Exceptions:
--   ORA-20000: Table does not exist or insufficient privileges
--   ORA-20001: Bad input value
--   ORA-20002: Bad user statistics table, may need to upgrade it
--   ORA-20005: object statistics are locked
--
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128

gather_table_STATS使用

在 gather_table_stats 存储过程的所有参数中,除了 ownname 和 tabname,其他的参数都有默认值。

所以我们在调用这个存储过程时, Oracle 会使用参数的默认值对表进行分析。

如:

SQL> exec dbms_stats.gather_table_STATS(user,'T');

PL/SQL procedure successfully completed
  • 1
  • 2
  • 3

oracel就会对当前用户下的T表按照所有参数的默认值进行分析,其中user是一个变量,用来返回当前的用户信息。

当然你也可以指定用户名,比如:


SQL> exec dbms_stats.gather_table_STATS('cc','xgj');

PL/SQL procedure successfully completed
  • 1
  • 2
  • 3
  • 4

对cc用户下的xgj表进行分析。

查看gather_table_STATS参数当前的默认值

如果想查看当前的默认值,可以使用 dbms_stats.get_param 函数来获取:

比如查看method_opt的默认值:

SQL>  select dbms_stats.get_param('method_opt') from dual;
  • 1

参数说明

参数的说明: 
estimate_percent 参数

这个参数是一个百分比值,它告诉分析包需要使用表中数据的多大比例来做分析。

理论上来讲,采样的数据越多,得到的信息就越接近于实际, CBO做出的执行计划就越优化,但是,采样越多,消耗的系统资源必然越多。 对系统的影响也越大。

所以对于这个值的设置,要根据业务情况来。

如果数据的直方图分布比较均匀,就可以使用默认值: AUTO_SAMPLE_SIZE,即让 Oracle 自己来判断采样的比例。

有时,特别是对于批量加载的表,我们可以预估表中的数据量,可以人工地设置一个合理的值。 一般,对于一个有 1000 万数据的表分区,可以把这个参数设置为 0.000001.


Method_option 参数

这个参数用来定义直方图分析的一些值。 
FOR ALL [INDEXED | HIDDEN] COLUMNS [size_clause] 
FOR COLUMNS [size clause] column|attribute [size_clause] [,column|attribute 
[size_clause]…] 
这里给出了 4 种指定哪些列进行分析的方式: 
( 1) 所有列: for all column 
( 2) 索引列:只对有索引的列进行分析, for all indexed columns 
( 3) 影藏列:只对影藏的列进行分析, for all hidden columns 
( 4) 显 示 指 定 列 : 显 示 的 指 定 那 些 列 进 行 分 析 , for columns columns_name 
该参数默认值: for all columns size auto.


degree 参数

用来指定分析时使用的并行度。 有以下这些设置:

(1) Null: 如果设置为 null, Oracle 将使用被分析表属性的并行度,比如表在创建时指定的并行度,或者后者使用 alter table 重新设置的并行度。 
(2) 一个数值: 可以显示地指定分析时使用的并行度。 
(3) Default_degree: 如果设置为 default, Oracle 将根据初始化参数中相关参数的设置来决定使用的并行度。

这个参数的默认值是 Null.即通过表上的并行度属性来决定分析使用的并行度。

当需要分析的表或表分区非常大,并且系统资源比较充分的时候,就可以考虑使用并行的方式来做分析,这样就会大大提高分析的速度。 相反,如果你的系统资源比较吃紧,那么启用并行可能会适得其反。


no_invalidate

no_invalidate:缺省值是DBMS_STATS.AUTO_INVALIDATE. 
如果想要dbms_stats分析立马见效,需要使用no_invalidate=false option或者DBA自己手工invalidate游标。


Granularity

分析的粒度,有以下几个配置: 
( 1) ALL : 将会对表的全局( global),分区,子分区的数据都做分析 
( 2) AUTO: Oracle 根据分区的类型,自动决定做哪一种粒度的分析。 
( 3) GLOBAL:只做全局级别的分析。 
( 4) GLOBAL AND PARTITION: 只对全局和分区级别做分析,对子分区不做分析,这是和 ALL 的一个区别。 
( 5) PARTITION: 只在分区级别做分析。 
( 6) SUBPARTITION: 只在子分区做分析。

在生产环境中,特别是 OLAP 或者数据仓库的环境中,这个参数的设置会直接影响到 CBO 的执行计划选择。


在 OLAP 或者数据仓库系统中,经常有这样的事情,新创建一个分区,将批量的数据(通常是很大的数据)加载到分区中,对分区做分析,然后做报表或者数据挖掘。

在理想的情况下,对表的全局,分区都做分析,这样才能得到最充 
足的数据,但是通常这样的表都非常大,如果每增加一个分区都需要做一次全局分析,那么会消耗极大的系统资源。

但是如果只对新加入的分区进行分区而不做全局分析, oracle 在全局范围内的信息就会不准确。

该参数在默认情况下, DBMS_STATS 包会对表级(全局),分区级(对应参数 partition)都会进行分析。

如果把 cascade 设置为 true,相应索引的全局和分区级别也都会被分析。

如果只对分区级进行分析,而全局没有分析,那么全局信息没有更新,依然会导致 CBO 作出错误的执行计划。

所以当一些新的数据插入到表中时,如果对这些新的数据进行分析,是一个非常重要的问题。

一般参考如下原则:

( 1) 看一下新插入的数据在全表中所占的比例,如果所占比例不是很大,那么可以考虑不做全局分析,否则就需要考虑,一句是业务的实际运行情况。 
( 2) 采样比例。 如果载入的数据量非常大,比如上千万或者更大,就要把采样比例压缩的尽可能地小,但底线是不能影响 CBO做出正确的执行计划,采样比例的上线是不能消耗太多的资源而影响到业务的正常运行。 
( 3) 新加载的数据应该要做分区级的数据分析。 至于是否需要直方图分析,以及设置多少个 buckets( size 参数指定),需要 DBA依据数据的分布情况进行考虑,关键是视数据的倾斜程度而定。


GATHER_SCHEMA_STATS

这个存储过程用于对某个用户下所有的对象进行分析。如果你的数据用户对象非常多,单独对每个对象进行分析设定会非常不方便,这个存储过程就很方便。

它的好处在于如果需要分析的对象非常多,将可以大大降低 DBA 的工作量,不足之处是所有分析使用相同的分析策略,可能会导致分析不是最优。 所以要根据实际情况来决定。


GATHER_INDEX_STATS

该 存 储 过 程 用 于 对 索 引 的 分 析 , 如 果 我 们 在 使 用 
DBMS_STATS.GATHER_TABLES_STATS 的分析时设置参数 cascade=>true。

那么 Oracle 会同时执行这个存储过程来对索引进行分析。


DBMS_STATS 包管理功能

获取分析数据

GET_COLUMN_STATS Procedures 
GET_INDEX_STATS Procedures 
GET_SYSTEM_STATS Procedure 
GET_TABLE_STATS Procedure

这四个存储过程分别为用户获取字段,索引,表和系统的统计信息。 它的用法是首先定义要获取性能指标的变量,然后使用存储过程将性能指标的值赋给变量,最后将变量的值输出。


设置分析数据

SET_COLUMN_STATS Procedures 
SET_INDEX_STATS Procedures 
SET_SYSTEM_STATS Procedure 
SET_TABLE_STATS Procedure

这几个存储过程允许我们手工地为字段,索引,表和系统性能数据赋值。 它的一个用处是当相应的指标不准确导致执行计划失败时,可以使用这种方法手工地来为这些性能数据赋值。 在极端情况下,这也不失为一个解决问题的方法。

SET_TABLE_STATS 实例演示:

SQL> select count(1)  from t;

  COUNT(1)
----------
     33872

SQL> exec dbms_stats.set_table_stats(user,'t',numrows => 100000);

PL/SQL procedure successfully completed

##执行sql
SQL>select * from t;
.....省略输出 

SQL> select a.SQL_ID ,a.CHILD_NUMBER,a.SQL_TEXT from v$sql a where a.SQL_TEXT like 'select * from t ';

SQL_ID CHILD_NUMBER SQL_TEXT
------------- ------------ --------------------------------------------------------------------------------
ckzaraqcmkr2f            0 select * from t
SQL> select * from table(dbms_xplan.display_cursor('ckzaraqcmkr2f',0));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  ckzaraqcmkr2f, child number 0
-------------------------------------
select * from t
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |    18 (100)|          |
|   1 |  TABLE ACCESS FULL| T    |   100K|   390K|    18   (6)| 00:00:01 |
--------------------------------------------------------------------------

13 rows selected

SQL> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

可以看到,通过这种方式,我们轻松地改变了T表的统计信息,T表中本来有3W多条记录,通过dbms_stats.set_table_stats将表的记录更改为100000条。这样,在执行计划中,CBO得到表上的记录数为100000条,铜鼓哦这种方式可以非常容易的模拟出我们希望的各种性能数据


删除分析数据

DELETE_COLUMN_STATS Procedure 
DELETE_DATABASE_STATS Procedure 
DELETE_DICTIONARY_STATS Procedure 
DELETE_FIXED_OBJECTS_STATS Procedure 
DELETE_INDEX_STATS Procedure 
DELETE_SCHEMA_STATS Procedure 
DELETE_SYSTEM_STATS Procedure 
DELETE_TABLE_STATS Procedure

当性能数据出现异常导致 CBO 判断错误时,为了立刻修正这个错误,删除性能数据也是一种补救的方法,比如删除表的数据,让 CBO 重新对表做动态采样分析,得到一个正确的结果。

它可以删除字段,数据库,数据字典,基表,索引,表等级别的性能数据。另外这也为我们模拟各种测试环境提供了一种便捷的方式

SQL> exec dbms_stats.delete_table_stats(user,'t');

PL/SQL procedure successfully completed

SQL> 
  • 1
  • 2
  • 3
  • 4
  • 5

这里写图片描述


保存分析数据

CREATE_STAT_TABLE Procedure 
DROP_STAT_TABLE Procedure

可以用这两个存储过程创建一个表,用于存放性能数据,这样有利于对性能数据的管理,也可以删除这个表。


导入和导出分析数据

EXPORT_COLUMN_STATS Procedure 
EXPORT_DATABASE_STATS Procedure 
EXPORT_DICTIONARY_STATS Procedure 
EXPORT_FIXED_OBJECTS_STATS Procedure 
EXPORT_INDEX_STATS Procedure 
EXPORT_SCHEMA_STATS Procedure 
EXPORT_SYSTEM_STATS Procedure 
EXPORT_TABLE_STATS Procedure

IMPORT_COLUMN_STATS Procedure 
IMPORT_DATABASE_STATS Procedure 
IMPORT_DICTIONARY_STATS Procedure 
IMPORT_FIXED_OBJECTS_STATS Procedure 
IMPORT_INDEX_STATS Procedure 
IMPORT_SCHEMA_STATS Procedure 
IMPORT_SYSTEM_STATS Procedure 
IMPORT_TABLE_STATS Procedure

这些存储过程可以将已经有的性能指标导入到用户创建好的表中存放,需要时,可以从表中倒回来。


锁定分析数据

LOCK_SCHEMA_STATS Procedure 
LOCK_TABLE_STATS Procedure

UNLOCK_SCHEMA_STATS Procedure 
UNLOCK_TABLE_STATS Procedure

可能在某些时候,我们觉得当前的统计信息非常好,执行计划很准确,并且表中数据几乎不变化,那么可以使用 LOCK_TABLE_STATS Procedure 来锁定表的统计信息,不允许对表做分析或者设定分析数据。

当表的分析数据被锁定之后,相关的所有分析数据,包括表级,列级,直方图,索引的分析数据都将被锁定,不允许被更新。


分析数据的恢复

RESET_PARAM_DEFAULTS Procedure 
RESTORE_DICTIONARY_STATS Procedure 
RESTORE_FIXED_OBJECTS_STATS Procedure 
RESTORE_SCHEMA_STATS Procedure 
RESTORE_SYSTEM_STATS Procedure 
RESTORE_TABLE_STATS Procedure

比如我们重新分析了表,发现分析的数据导致了 CBO 选择了错误的执行计划,为了挽救这种局面,可以将统计信息恢复到从前的那个时间点,也就是 CBO 执行计划正确的时间点,先解决这个问题,再来分析问题的原因。

SQL> select dbms_stats.get_stats_history_availability from dual;

GET_STATS_HISTORY_AVAILABILITY
------------------------------------------
08-DEC-16 11.13.35.798721000 PM +08:00

SQL> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

单表分析:
    分区表:
    BEGIN
      DBMS_STATS.GATHER_TABLE_STATS(ownname => 'cc',
                                    tabname => 't',
                                    estimate_percent => 100,
                                    no_invalidate => FALSE,--立马生效
                                    degree => 8,
                                    granularity => 'ALL', --分区
                                    cascade => TRUE);
    END;
    /
 非分区表:
    BEGIN
      DBMS_STATS.GATHER_TABLE_STATS(ownname => 'cc',
                                    tabname => 't',
                                    estimate_percent => 100,
                                    no_invalidate => FALSE,--立马生效
                                    degree => 8,
                                    cascade => TRUE);
    END;

猜你喜欢

转载自blog.csdn.net/thy822/article/details/80243417