数据库高效优化:架构、规范与SQL技巧
上QQ阅读APP看书,第一时间看更新

4.1 统计信息分类

统计信息可大致分为系统统计信息、对象统计信息、数据字典统计信息、内部对象统计信息等,下面分别介绍。

4.1.1 系统统计信息

Oracle数据库从9i开始增加了收集操作系统统计信息的功能。通过收集到的统计信息判断操作系统的CPU、I/O的处理能力。这为优化器选择执行计划提供了额外的判断依据。通过使用系统统计信息,优化器能够更加准确地判断、评价CPU和I/O代价,进而选择更好的查询计划。根据度量I/O子系统的方法不同,可将系统统计信息分为两类。

·非工作量统计信息(noworkload statistics)

·工作量统计信息(workload statistics)

这两者的区别是,前者使用人工基准测试,后者使用应用程序基准测试。这里解释一下,所谓人工基准测试是指并非实际运行的程序产生的工作量。人工基准测试的主要目的是通过执行近似的操作,以模拟应用程序的负载。虽然能够以轻易可控的方式运行,但通常情况下无法产生像应用程序基准测试那么好的性能数据。所谓应用程序基准测试是指基于实际应用程序正常操作产生的工作量进行的。通常可以很好地提供实际运行系统的性能信息。关于收集方法,后面会有专门说明。下面对系统统计信息项加以说明。

1.指标项说明

非工作量统计信息和工作量统计信息这两类所包含的指标不同,在计算时会进行一定的换算。

(1)非工作量统计信息

从10g开始,非工作量统计信息总是可用的,主要包含以下指标项。

·CPUSPEEDNW:代表无负载CPU速度。CPU速度为每秒CPU周期数,也就是一个CPU一秒能处理的操作数,单位是百万次/秒。

·IOSEEKTIM:I/O查找时间,也就是平均寻道时间,其等于查找时间、延迟时间、OS负载时间三者之和,单位为毫秒,默认为10。

·IOTFRSPEED:I/O传输速度,也就是平均每毫秒从磁盘传输的字节数,单位为字节/毫秒,默认为4096。

(2)工作量统计信息

工作量统计信息只有在显式地收集以后才可用。要进行显式收集,就不能使用空闲的系统,因为数据库引擎要利用正常的数据库负载来评估I/O子系统。另一方面,衡量CPU速度的方法与进行非工作统计时一样。工作量统计信息主要包含以下指标项。

·CPUSPEED:代表有负载CPU速度。CPU速度为每秒CPU周期数,也就是一个CPU一秒能处理的操作数,单位为百万次/秒。

·SREADTIM:随机读取单块的平均时间,单位为毫秒。

·MREADTIM:顺序读取多块的平均时间,也就是多块平均读取时间,单位为毫秒。

·MBRC:平均每次读取的块数量,单位为块数。

·MAXTHR:最大I/O吞吐量,单位为字节/秒。

·SLAVETHR:并行处理中从属线程的平均I/O吞吐量,单位为字节/秒。

2.数据字典统计信息查询

系统统计信息保存在aux_stats表里面。Oracle没有提供数据字典视图来供外部表访问。我们可以通过对这个内表的访问,了解系统统计信息各个方面的情况。例如下面的命令,可查看系统统计信息收集的时间及状态。


[sys@testdb] SQL> col pval2 format a20
[sys@testdb] SQL> select pname,pval1,pval2
2  fromsys.aux_stats$
3  wheresname='SYSSTATS_INFO';
PNAME                               PVAL1 PVAL2
------------------------------ ---------- --------------------
DSTART                                    08-24-2013 12:04
DSTOP                                     08-24-2013 12:04
FLAGS                                   1
STATUS                                    COMPLETED

如果收集是正确的,则显示为COMPLETED状态。下面的命令可查询系统统计信息的结果集。


[sys@testdb] SQL> select pname,pval1 from sys.aux_stats$ where sname='SYSSTATS_MAIN';
PNAME                               PVAL1
------------------------------ ----------
CPUSPEED
CPUSPEEDNW                     3074.07407
IOSEEKTIM                              10
IOTFRSPEED                           4096
MAXTHR
MBRC
MREADTIM
SLAVETHR
SREADTIM

3.相关操作

针对系统统计信息,可以有多种操作,下面简单说明一下。

(1)收集统计信息

针对非工作量和工作量的统计信息,收集的方法是不同的。针对非工作量的系统统计信息,可采用如下方法收集:


dbms_stats.gather_system_stats(gathering_mode=>'noworkload');

针对工作量的统计信息,可使用多种方法进行收集。一种方法是,执行两次收集动作,在两次快照之间计算其差值。具体方法参考如下:


dbms_stats.gather_system_stats(gathering_mode=>'start');
wait a moment
dbms_stats.gather_system_stats(gathering_mode=>'stop');

这里关于等待时间需要注意,数据库引擎并不控制数据库负载,因此必须等待足够的时间来产生一个有代表性的负载之后再进行另一次快照,一般等待至少30分钟。

另一种方法是,立即启动收集一个快照,而第二次收集快照动作在指定时长后执行。这个处理过程并不会一直持续,它会通过系统的调度工具完成。


dbms_stats.gather_system_stats(gathering_mode=>'interval', interval=>N);
*上述过程中,参数interval就是指定收集间隔时长

(2)设置统计信息

除了利用上面的方法收集系统统计信息外,还可以手工设置系统统计信息。但一般不建议这样做,有一定操作风险。手工设置系统统计信息如下:


begin
dbms_stats.delete_system_stats();
dbms_stats.set_system_stats(pname=>'CPUSPEED',pvalue=>772);
dbms_stats.set_system_stats(pname=>'SREADTIM',pvalue=>5.5);
dbms_stats.set_system_stats(pname=>'MREADTIM',pvalue=>19.4);
dbms_stats.set_system_stats(pname=>'MBRC',pvalue=>53);
dbms_stats.set_system_stats(pname=>'MAXTHR',pvalue=>1243434334);
dbms_stats.set_system_stats(pname=>'SLAVETHR',pvalue=>1212121);

(3)删除统计信息

如果感觉系统收集的统计信息有问题,也可以采用下面的方式进行删除。


execdbms_stats.delete_system_stats;

4.系统统计信息对优化器的影响

系统统计信息会直接影响优化器的成本计算。如果存在工作量统计,则优化器会使用它而忽略非工作量统计信息。如果工作量统计信息不正确,那么数据库会使用非工作量统计信息。但要注意,查询优化器会运行一些健康检查,可能禁用或部分替换工作量统计信息。

在未引入系统统计信息之前,CBO所计算的成本值全部是基于I/O来计算的;在Oracle引入了系统统计信息之后,实际上就额外地引入了CPU成本计算模型。从此之后,CBO所计算的成本值就不再仅仅包含I/O成本,而是包含I/O成本和CPU成本两部分。CBO在计算成本的时候就会分别对它们进行计算,并将计算出的I/O成本和CPU成本值的总和作为目标SQL的新成本值。

4.1.2 对象统计信息

1.表统计信息

表是数据库里最基础的对象。下面通过一个简单例子,看看表都有哪些常见的统计信息。

在下面的例子中,我们手工创建了一个表,然后收集了相关统计信息,最后查看数据字典,得到相关的统计信息。


[hf@testdb] SQL> create table t1 as select * from dba_objects;
Table created.

[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user, 't1');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select table_name,num_rows,blocks,empty_blocks,avg_space,
      chain_cnt,avg_row_len
2  fromuser_tables t
3  wheretable_name='T1';
TABLE_NAME   NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT AVG_ROW_LEN
---------- ---------- ---------- ------------ ---------- ---------- -----------
T1              86273       1260            0          0          0          98

其中,统计信息的含义如下。

·num_rows:数据的行数。

·blocks:高水位线下的数据块个数。

·empty_blocks:高水位线以上的数据块个数。dbms_stats不计算这个值,被设置为0。

·avg_space:数据块中平均空余空间(字节)。dbms_stats不计算这个值,被设置为0。

·chain_cnt:行链接和行迁移的数目。dbms_stats不计算这个值,被设置为0。

·avg_row_len:行平均长度(字节)。

(1)高水位线

我们在前面表的统计信息中可以看到一个概念——高水位线(HWM),这是一个比较重要的概念。当我们开始向表插入数据时,第1个块已经放不下后面新插入的数据。此时,Oracle将高水位线之上的块用于存储新增数据;同时,高水位线本身也向上移。也就是说,当不断插入数据时,高水位线会不断上移。这样,在高水位线之下的,就表示使用过的块;高水位线之上的,就表示已分配但从未使用过的块。高水位线在插入数据时,当现有空间不足而进行空间的扩展时会向上移,但删除数据时不会下移。Oracle不会释放空间以供其他对象使用。

Oracle的全表扫描是读取高水位线以下的所有块。当发出一个全表扫描时,Oracle始终必须从段开头一直扫描到高水位线,即使它什么也没有发现。该任务延长了全表扫描的时间。下面通过一个示例说明一下。


[hf@testdb] SQL> create table t1 as select * from dba_objects;
Table created.

[hf@testdb] SQL> insert into t1 select * from t1;
86274 rows created.

[hf@testdb] SQL> insert into t1 select * from t1;
172548 rows created.

[hf@testdb] SQL> insert into t1 select * from t1;
345096 rows created.

[hf@testdb] SQL> commit;
Commit complete.

[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user, 't1');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> exec sys.show_space('t1','auto');
Total Blocks............................10240
Total Bytes.............................83886080
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................4
Last Used Ext BlockId...................13312
Last Used Block.........................1024
PL/SQL procedure successfully completed.

这里使用了一个自定义的过程show_space,其具体定义可以在网上搜到,这里不再赘述。通过这个方法可以观察到一个表的空间使用情况,HWM的计算公式为:HWM=Total Blocks–Unused Blocks。针对上例,其高水位线就是10 240。

下面的语句执行了一个全表扫描。通过之前的说明可知,数据库会扫描高水位线下的全部数据块。对应的统计信息consistent gets和physical reads可见。


[hf@testdb] SQL> set autotracetraceonly
[hf@testdb] SQL> select count(*) from t1;
-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |  2749   (1)| 00:00:33 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T1   |   690K|  2749   (1)| 00:00:33 |
-------------------------------------------------------------------
Statistics
----------------------------------------------------------
9881  consistent gets
9873  physical reads

删除数据后,重复下面的行为,从输出可见其consistent gets变化不大,physical reads减少是因为数据块被缓存的关系。


[hf@testdb] SQL> set autotrace off
[hf@testdb] SQL> delete from t1;
690192 rows deleted.

[hf@testdb] SQL> commit;
Commit complete.

[hf@testdb] SQL> set autotracetraceonly
[hf@testdb] SQL> select count(*) from t1;
-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |  2749   (1)| 00:00:33 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T1   |   690K|  2749   (1)| 00:00:33 |
-------------------------------------------------------------------
Statistics
----------------------------------------------------------
9888  consistent gets
2484  physical reads

此时,查看其高水位线,没有变化。也就是说,delete操作不会降低高水位线。


[hf@testdb] SQL> set autotrace off
[hf@testdb] SQL> exec sys.show_space('t1','auto');
Total Blocks............................10240
Total Bytes.............................83886080
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................4
Last Used Ext BlockId...................13312
Last Used Block.........................1024
PL/SQL procedure successfully completed.

Truncate操作后,整体读取的数据块减少了。原因就是其高水位线下降了,从原来的10240到现在的(8-5)。因为扫描的数据块少了,所以其一致性读、物理读指标也下降了。


[hf@testdb] SQL> truncate table t1;
Table truncated.

[hf@testdb] SQL> set autotracetraceonly
[hf@testdb] SQL> select count(*) from t1;
-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |  2749   (1)| 00:00:33 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T1   |   690K|  2749   (1)| 00:00:33 |
-------------------------------------------------------------------
Statistics
----------------------------------------------------------
7  consistent gets
0  physical reads

[hf@testdb] SQL> set autotrace off
[hf@testdb] SQL> exec sys.show_space('t1','auto');
Total Blocks............................8
Total Bytes.............................65536
Unused Blocks...........................5
Unused Bytes............................40960
Last Used Ext FileId....................4
Last Used Ext BlockId...................2856
Last Used Block.........................3

PL/SQL procedure successfully completed.

(2)临时表

临时表是一类特殊的数据对象,其很多行为与普通表不同。对于数据库来说,有两种临时表:一种是基于会话(on Commit Preserve Row)的临时表;一种是基于事务(on Commit Delete Row)的临时表。无论是基于事务还是基于会话的临时表,对于其他会话都是不可见的。换句话说,数据只存在于当前会话中。基于事务的临时表,在本会话中只要有提交动作,数据就会立即消失;基于会话的临时表在SESSION生存期内提交数据仍然存在,并且可以回滚,没退出会话之前和普通表的操作没有什么区别。

针对临时表而言,默认是不收集统计信息的,可以使用dbms_stats.gather_schema_stats这个过程来收集,但是需要修改属性gather_tmp的值,将其由默认的false,修改为true。在收集统计信息时,最终的统计信息是最后一个执行收集动作的会话所能看到的数据。不过需要注意的是,可以统计基于会话的临时表,不能统计基于事务的临时表。

由于临时表是全局的,但收集的统计信息是在某个会话下做的,不同会话之间看到的数据是不同的。因此在使用临时表时需要注意,有时收集统计信息反而会产生问题,此时可考虑走默认的动态采样的方式。

下面创建了一个基于会话的临时表。

下面通过一个示例加以说明。


[hf@testdb] SQL> create global temporary table t_temp2
2  on commit preserve rows
3  as select * from dba_objects where 1=2;
Table created.

[hf@testdb] SQL> create index idx_object_id on t_temp2(object_id);
Index created.

在一个会话中插入少量数据(10条),后面简称为“会话1”。


[hf@testdb] SQL> select sid from v$mystat where rownum=1;
       SID
----------
        20

[hf@testdb] SQL> insert into t_temp2 select * from dba_objects where rownum<=10;
10 rows created.

[hf@testdb] SQL> commit;
Commit complete.

在一个会话中插入大量数据(20万条),后面简称为“会话2”。


[hf@testdb] SQL> select sid from v$mystat where rownum=1;
       SID
----------
        15

[hf@testdb] SQL> insert into t_temp2 select * from dba_objects where rownum<=50000;
50000 rows created.

[hf@testdb] SQL> insert into t_temp2 select * from dba_objects where rownum<=50000;
50000 rows created.

[hf@testdb] SQL> insert into t_temp2 select * from dba_objects where rownum<=50000;
50000 rows created.

[hf@testdb] SQL> insert into t_temp2 select * from dba_objects where rownum<=50000;
50000 rows created.

[hf@testdb] SQL> commit;
Commit complete.

在“会话1”中执行查询语句,从输出中可见,这里使用了动态采样,因为默认情况下临时表是不收集统计信息的。


[hf@testdb] SQL> select count(*) from t_temp2 where object_id between 10000 and 50000;
--------------------------------------------------------------------------------
| Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |             |     1 |    13 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE  |             |     1 |    13 |            |          |
|*  2 |   INDEX RANGE SCAN
                         |IDX_OBJECT_ID|     1 |    13 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("OBJECT_ID">=10000 AND "OBJECT_ID"<=50000)

Note
-----
   - dynamic sampling used for this statement (level=2)

Statistics
----------------------------------------------------------
9  recursive calls
0  db block gets
9  consistent gets

在“会话2”中执行查询语句,从输出中可见,这里也使用了动态采样,但下面显示的“一致性读”明显不同,这显然是由于不同会话动态采样后分析对象的大小不同。


[hf@testdb] SQL> select count(*) from t_temp2 where object_id between 10000 and 50000;
--------------------------------------------------------------------------------
| Id  | Operation      | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT
                       |               |     1 |    13 |   163   (0)| 00:00:02 |
|   1 |  SORT AGGREGATE
                       |               |     1 |    13 |            |          |
|*  2 |   INDEX FAST FULL SCAN
                       | IDX_OBJECT_ID |   138K|  1761K|   163   (0)| 00:00:02 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("OBJECT_ID">=10000 AND "OBJECT_ID"<=50000)

Note
-----
   - dynamic sampling used for this statement (level=2)

Statistics
----------------------------------------------------------
0  recursive calls
0  db block gets
961  consistent gets

我们在“会话1”中收集了统计信息。从后面的查询可见,表的记录数为10。这是准确的,因为“会话1”能看到的记录就是10条。


[hf@testdb] SQL> exec dbms_stats.gather_schema_stats( 'hf', gather_temp=>TRUE);

PL/SQL procedure successfully completed.

[hf@testdb] SQL> select table_name,num_rows,blocks,chain_cnt,avg_row_len,
      global_stats,user_stats,sample_size,to_char(t.last_analyzed,'yyyy-mm-dd') aly_d
2  fromdba_tables t
3  where owner='HF' and table_name='T_TEMP2';
TABLE_NAME  NUM_ROWS  BLOCKS CHAIN_CNT AVG_ROW_LEN GLO USE SAMPLE_SIZE ALY_D
---------- -------- ------- --------- ----------- --- --- ----------- ----------
T_TEMP2          10       1         0          75 YES  NO          10 2014-11-10

在“会话1”中再次执行上面的SQL,从输出可见其执行计划不变,并且没有动态采样的字样了。此外,收集的执行信息部分,一致性读为1,这比下面采用动态采样获得的方式更加精准。对比下面统计信息中的blocks,可以完全对应上。


[hf@testdb] SQL> select count(*) from t_temp2 where object_id between 10000 and 50000;
--------------------------------------------------------------------------------
| 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
                        | IDX_OBJECT_ID |     1 |     3 |     1  (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("OBJECT_ID">=10000 AND "OBJECT_ID"<=50000)

Statistics
----------------------------------------------------------
0  recursive calls
0  db block gets
1  consistent gets

在“会话2”中重新查看一下统计信息,从中可见看到的还是“会话1”收集的状态。也就是说,各个会话看到的数据是不同的,但是看到的统计信息是一个。


[hf@testdb] SQL> select table_name,num_rows,blocks,chain_cnt,avg_row_len,
      global_stats,user_stats,sample_size,to_char(t.last_analyzed,'yyyy-mm-dd') aly_d
2  fromdba_tables t
3  where owner='HF' and table_name='T_TEMP2';
TABLE_NAME NUM_ROWS   BLOCKS  CHAIN_CNT AVG_ROW_LEN GLO USE SAMPLE_SIZE  ALY_D
---------- -------- -------- --------- ----------- --- --- ----------- ----------
T_TEMP2          10        1         0          75 YES NO          10 2014-11-10

“会话2”重新执行下面的SQL语句,发现其执行计划出现了很大偏差。从原有的“INDEX FAST FULL SCAN”变为“INDEX RANGE SCAN”。这显然不是我们希望看见的,由于收集了临时表的统计信息,反而造成执行计划效率低下。


[hf@testdb] SQL> select count(*) from t_temp2 where object_id between 10000 and 50000;
--------------------------------------------------------------------------------
| 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|IDX_OBJECT_ID|     1 |    3 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("OBJECT_ID">=10000 AND "OBJECT_ID"<=50000)

Statistics
----------------------------------------------------------
0  recursive calls
0  db block gets
481  consistent gets

在“会话2”中,删除了统计信息,执行计划又回到熟悉的“INDEX FAST FULL SCAN”,后面又显示了动态采样方式。


[hf@testdb] SQL> exec dbms_stats.delete_table_stats('hf', 't_temp2');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select count(*) from t_temp2 where object_id between 10000 and 50000;
--------------------------------------------------------------------------------
| Id  | Operation             | Name        |  Rows| Bytes|Cost (%CPU)| Time    |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |     1|   13 |   163 (0)| 00:00:02 |
|   1 |  SORT AGGREGATE       |             |     1|   13 |          |          |
|*  2 |   INDEX FAST FULL SCAN|IDX_OBJECT_ID|  138K| 1761K|   163 (0)| 00:00:02 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("OBJECT_ID">=10000 AND "OBJECT_ID"<=50000)

Note
-----
   - dynamic sampling used for this statement (level=2)

从上面的例子可见,临时表默认是采用动态采样的方式,这往往是一种比较合适的方式。由于不同会话看到的数据不同,因此直接收集的方式往往是不可靠的。但临时表这种“粗粒度”的管理方式,不利于生成精确的执行计划。因此语句中如果有临时表,需要关注因统计信息不准确导致的问题。

2.索引统计信息

索引是数据库里最常见的对象。下面通过一个简单的例子,看看索引都有哪些常见的统计信息。

下面的代码创建了一个索引,并查看其统计信息。因为显示格式问题,这里使用了一个过程print_table,后面将在附录中列出这个过程。


[hf@testdb] SQL> create table t1 as select * from dba_objects;
Table created.

[hf@testdb] SQL> create index idx_status on t1(status);
Index created.

[hf@testdb] SQL> exec print_table('select index_name,uniqueness,blevel,leaf_blocks,distinct_keys,num_rows,avg_leaf_blocks_per_key,avg_data_blocks_per_key,clustering_factor,global_stats,user_stats,sample_size,to_char(t.last_analyzed,''yyyy-mm-dd'') aly_d from user_indexes t where table_name=''T1'' and index_name=''IDX_STATUS''')
INDEX_NAME                    : IDX_STATUS
UNIQUENESS                    : NONUNIQUE
BLEVEL                        : 1
LEAF_BLOCKS                   : 205
DISTINCT_KEYS                 : 1
NUM_ROWS                      : 86274
AVG_LEAF_BLOCKS_PER_KEY       : 205
AVG_DATA_BLOCKS_PER_KEY       : 1232
CLUSTERING_FACTOR             : 1232
GLOBAL_STATS                  : YES
USER_STATS                    : NO
SAMPLE_SIZE                   : 86274
ALY_D                         : 2014-11-10
-----------------
PL/SQL procedure successfully completed.

下面看一下索引有哪些统计信息。

·num_rows:索引行。

·leaf_blocks:索引叶块数。

·distinct_keys:索引不同键数。

·blevel:索引的blevel分支层数(btree的深度,从root节点到leaf节点的深度。如果root节点也是leaf节点,那么这个深度就是0)。

·avg_leaf_blocks_per_key:每个键值的平均索引叶块数(每个键值的平均索引leaf块数,近似取整),如果是unique index或pk,这个值总是1)。

·avg_data_blocks_per_key:每个键值的平均索引数据(表)块数。

·clustering_factor:索引的聚簇因子(一个度量标准,用于索引的有序度和表混乱度之间的比较)。

我们在上面的索引统计信息中看到一个概念——聚簇因子(clustering_factor),这是一个比较重要的概念,用于标识表中数据的存储顺序和某些索引字段顺序的符合程度。Oracle按照索引块所存储的rowid来标识相邻索引记录在表block中是否为相同块。如果索引中存在多条记录a、b、c、d……若b和a是同一个块,则比较c和b;若不在同一个块,则clustering_factor+1,然后比较d和c;若还不是同一个块,则clustering_factor+1……。这样计算下来,clustering_factor会是介于表块数量和表记录数之间的一个值。若clustering_factor接近块数量,则说明表中数据具有比较好的与索引字段一样排序顺序的存储,通过索引进行range scan的代价比较小(需要读取的块数比较少);若clustering_factor接近记录数,则说明数据和索引字段排序顺序差异很大,杂乱无章,需要通过索引进行range scan的代价比较大(需要读取的表块可能很多)。

下面通过一个示例,显示聚簇因子的一些使用问题。


[hf@testdb] SQL> create table t1 as select rownum id, object_name name from dba_objects
      whererownum<=50000;
Table created.

[hf@testdb] SQL> create index idx_t1 on t1(id);
Index created.

[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'t1',cascade => true);
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select blocks,num_rows from user_tables where table_name = 'T1';
    BLOCKS   NUM_ROWS
---------- ----------
       252      50000

[hf@testdb] SQL> select index_name, blevel, leaf_blocks, clustering_factor
      fromuser_indexes where table_name = 'T1';
INDEX_NAME                         BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ----------- -----------------
IDX_T1                                  1         110               240

上例中聚簇因子接近表的块数,性能很好。原因是创建表时指定的ID列是一个递增的顺序,所以索引顺序与表的存放顺序高度一致。

下面的代码使用了一个反转索引的方法。所谓反转索引,就是将每个列的字节顺序反转。这样做的目的是将顺序值打乱为随机散布的索引项。由这个例子可见,其聚簇因子剧增,接近了表的记录数,性能很差。


[hf@testdb] SQL> alter index idx_t1 rebuild reverse;
Index altered.

[hf@testdb] SQL> select blocks,num_rows from user_tables where table_name = 'T1';
    BLOCKS   NUM_ROWS
---------- ----------
       252      50000

[hf@testdb] SQL> select index_name, blevel, leaf_blocks, clustering_factor
      fromuser_indexes where table_name = 'T1';
INDEX_NAME                         BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ----------- -----------------
IDX_T1                                  1         111             49994

3.字段统计信息

数据库除了表、索引外,也会收集字段的统计信息。字段的统计信息分两类:一类是基本信息,另外一类是柱状图信息。我们后面会有专门的章节介绍柱状图,所以这里只谈基本信息。下面通过一个例子进行说明。


[hf@testdb] SQL> create table t1 as select * from dba_objects;
Table created.

[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'T1');
[hf@testdb] SQL> l
  1  select column_name,
  2         decode(t.data_type,
  3             'NUMBER',t.data_type||'('||decode(t.data_precision,null,t.data_length||')',t.data_precision||','||t.data_scale||')'),
  4             'DATE',t.data_type,
  5             'LONG',t.data_type,
  6             'LONG RAW',t.data_type,
  7             'ROWID',t.data_type,
  8             'MLSLABEL',t.data_type,
  9             t.data_type||'('||t.data_length||')')||' '||
 10         decode(t.nullable,
 11             'N','NOT NULL',
 12             'n','NOT NULL',
 13             NULL) col,
 14         num_distinct,num_nulls,density,avg_col_len,histogram,num_buckets
 15  from user_tab_cols t
 16* where table_name='T1'

 [hf@testdb] SQL> /

COLUMN_NAME     COL          NUM_DISTINCT NUM_NULLS    DENSITY AVG_COL_LEN HISTO  NUM_BUCKETS
-------------- ------------ ---------- ------- --------- ---------------- ----------
OWNER          VARCHAR2(30)         24       0 .041666667          6 NONE          1
OBJECT_NAME    VARCHAR2(128)     51744       0 .000019326         25 NONE          1
SUBOBJECT_NAME VARCHAR2(30)         89   86009 .011235955          2 NONE          1
OBJECT_ID      NUMBER(22)        86274       0 .000011591          5 NONE          1
DATA_OBJECT_ID NUMBER(22)         8583   77651 .000116509          2 NONE          1
OBJECT_TYPE    VARCHAR2(19)         44       0 .022727273          9 NONE          1
CREATED        DATE                889       0 .001124859          8 NONE          1
LAST_DDL_TIME  DATE               1004       0 .000996016          8 NONE          1
TIMESTAMP      VARCHAR2(19)       1044       0 .000957854         20 NONE          1
STATUS         VARCHAR2(7)           1       0          1          6 NONE          1
TEMPORARY      VARCHAR2(1)           2       0         .5          2 NONE          1
GENERATED      VARCHAR2(1)           2       0         .5          2 NONE          1
SECONDARY      VARCHAR2(1)           2       0         .5          2 NONE          1
NAMESPACE      NUMBER(22)           20       0        .05          3 NONE          1
EDITION_NAME  VARCHAR2(30)           0   86274          0          0 NONE          0

默认的情况下,数据库会为列收集基本信息,但不会收集柱状图信息。在使用dbms_stats.gather_table_stats收集表的统计信息时,未指定method_opt,则Oracle将采用FOR ALL COLUMNS SIZE AUTO选型。从10g开始,有一个内置的参数_column_tracking_level,可以通过它来控制是否监控列的使用。默认这个参数是打开的,此时如果某些倾斜列被频繁使用,则Oracle会在Auto模式下,自动为该列收集柱状图。

下面看看列统计信息的主要项。

·num_distinct:不同值的数目。

·num_nulls:字段值为null的数目。

·density:选择率。

·low_value:最小值,显示为内部存储的格式。注意,字符串列只存储前32字节。

·high_value:最大值,显示为内部存储的格式。注意,字符串列只存储前32字节。

·avg_col_len:列平均长度(字节)。

·histogram:是否有直方图统计信息。如果有,则是哪种类型。10g以后的版本才提供。

·NONE:没有直方图。

·FREQUENCY:基于频率类型。

·HEIGHT BALANCED:基于高度类型。

·num_buckets:直方图的桶数。

这里引入了一个很重要的概念——选择率。这个指标反映了字段的选择性。优化器通过选择率与记录数的乘积来获得基数。这是作为执行路径选择的一个重要依据。选择率的计算方法与字段是否存在柱状图有关,这里只介绍在字典不存在柱状图的情况下的计算方法,也就是没有柱状图的情况;有柱状图的情况后面的章节将单独介绍。对于没有柱状图的情况,字段选择率为1/num_distinct。下面通过一个示例说明。


[hf@testdb] SQL> create table t1 as select rownumid ,object_name,status from dba_objects where rownum<=50000;
Table created.

[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'T1');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select min(id),max(id) from t1;
MIN(ID)    MAX(ID)
---------- ----------
         1      50000

这里创建了一个测试表,表的ID字段范围是1~50 000。


[hf@testdb] SQL> select column_name,num_distinct,num_nulls,density,histogram,num_buckets
2  from user_tab_cols t
3  where table_name='T1';
COLUMN_NAME     NUM_DISTINCT  NUM_NULLS    DENSITY HISTO NUM_BUCKETS
--------------- ------------ ---------- ---------- ----- -----------
ID                     50000          0     .00002 NONE            1
OBJECT_NAME            28810          0  .00003471 NONE            1
STATUS                     1          0          1 NONE            1

从统计信息可见,ID字段的选择率为1/num_distinct=1/50000。


[hf@testdb] SQL> select * from t1 where id between 1 and 10000;
10000 rows selected.
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |   351K|    82   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |   351K|    82   (0)| 00:00:01 |
--------------------------------------------------------------------------

对ID字段执行了一个范围查询,这里评估出的基数为10 000。这是在全表5万条记录中选择出的1万条,选择率为1/50 000计算得来的。具体计算方法是:选择率×选择范围×记录数=评估基数,即1/50 000×10 000×50 000=10 000。

4.直方图信息

当数据字段的数据分布不均匀时,通过之前的字段统计信息优化器往往很难做出正确的估算。为了解决这一问题,Oracle引入了一种新的统计信息类型——直方图。简单来说,它就是来反映数据分布情况的一种统计信息。从原理上来讲,就是假定存在n个桶(Buckets),每个桶代表一个取值或者一个取值范围,将列中不同的值放入与之对应的桶中,通过这些桶的统计来得到列上数据分布的情况。

根据唯一值的数量和桶的个数,可以将直方图分为两种类型(在12c中,又细分为4种):基于频率的直方图和基于高度的直方图。下面针对这两种直方图分别加以说明。

(1)基于频率的直方图

当列的唯一值数量小于或等于桶允许的最大值(254)时,数据库会使用基于频率的直方图。每个值将会占据一个桶。每个桶的高低代表每个值出现的次数。下面通过一个图简单说明,如图4-1所示。

图4-1 基于频率的直方图

下面通过一个实际的例子,帮大家体会一下基于频率的直方图。


[hf@testdb] SQL> execute dbms_random.seed(0);
PL/SQL procedure successfully completed.

[hf@testdb] SQL> create table t1 
2  as
3  select trunc(dbms_random.value(1,10))val
4  from dual
5  connect by rownum<= 10000;
Table created.

这里创建了一个测试表,共插入了10 000条记录,其中的VAL字段为1~9之间的随机整数。


[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'t1',method_opt => 'for columns valsize 254');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select num_distinct,num_buckets,histogram
2  from user_tab_columns
3  where table_name='T1' and column_name='VAL';
NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------ ----------- ---------------
           9           9 FREQUENCY

这里收集了统计信息,注意收集统计的选型使用了“for columns val size 254”。这表示采用254个桶来收集VAL字段的统计信息。因为这个字段的不同值只有9个,所以会使用基于频率的直方图。从后面的查询也可以看出,这个表有9个不同的值,使用了9个桶,整个直方图的类型为基于频率的直方图。


[hf@testdb] SQL> select endpoint_number, endpoint_value
2  from user_tab_histograms
3  where column_name = 'VAL' and table_name = 'T1'
4  order by endpoint_number;
ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
           1160              1
           2333              2
           3398              3
           4506              4
           5674              5
           6718              6
           7826              7
           8907              8
          10000              9
9 rows selected.

可以从user_tab_histograms视图中查看直方图的具体信息。这里有两个主要字段,其含义分别如下。

·ENDPOINT_VALUE:该值本身。如果字段是NUMBER类型,可以直接显示;对于非数字类型(VARCHAR2、CHAR、NVARCHAR2、NCHAR和RAW)必须要进行转换。

·ENDPOINT_NUMBER:取值的累计出现次数。当前endpoint_number减去上一个endpoint_number就是当前行出现的次数。


[hf@testdb] SQL> select val,count(*) from t1 group by val order by val;
       VAL   COUNT(*)
---------- ----------
         1       1160
         2       1173
         3       1065
         4       1108
         5       1168
         6       1044
         7       1108
         8       1081
         9       1093

对照这个查询与上面直方图的输出,就很容易理解了。VAL=1的记录,共有1160个;VAL=2的记录,共有1173(即2333-1160);以此类推。

(2)基于高度的直方图

当列的唯一值数量大于桶数时,数据库会基于高度的直方图反映数据分布,每个桶容纳相同数量的值。下面通过一个图简单说明,如图4-2所示。

图4-2 基于高度的直方图

下面通过一个实际的例子,帮大家体会一下基于高度的直方图。


[hf@testdb] SQL> execute dbms_random.seed(0);
PL/SQL procedure successfully completed.

[hf@testdb] SQL> create table t1 
2  as
3  select trunc(dbms_random.value(1,100))val
4  from dual
5  connect by rownum<= 10000;
Table created.

这里创建了一个测试表,共插入了10 000条记录。其中的VAL字段为1~99之间的随机整数。


[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'t1',method_opt => 'for columns val size 50');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select num_distinct,num_buckets,histogram
2  from user_tab_columns
3  where table_name='T1' and column_name='VAL';
NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------ ----------- ---------------
          99          50 HEIGHT BALANCED

这里收集了统计信息,注意收集统计的选型使用了“for columns val size 50”。这表示采用50个桶来收集VAL字段的统计信息。因为这个字段的不同值只有99个,因此会使用基于高度的直方图。从后面的查询也可以看出,这个表有99个不同的值,使用了50个桶,整个直方图的类型为基于高度的直方图。


[hf@testdb] SQL> select endpoint_number, endpoint_value
2  from user_tab_histograms
3  where column_name = 'VAL' and table_name = 'T1'
4  order by endpoint_number;
ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
              0              1
              1              2
              2              4
              3              6
              4              8
              5             10
              6             12
              7             14
              8             16
              9             18
             10             19
...
             50             99
51 rows selected.

可以从user_tab_histograms视图中,查看直方图的具体信息。这里有两个主要字段,其含义分别如下:

·ENDPOINT_VALUE:列的数值。该列是NUMBER类型,如果直方图列是非数字类型则需要转换,且只取字段的前六个字节(不是字符并记录到数据字典中)。

·ENDPOINT_NUMBER:桶号。

如何解读上面的输出呢?上面输出代表VAL=1,占据了0号桶;VAL=2,占据了1号桶;VAL=3、4,占据了2号桶;VAL=5、6占据了3号桶;以此类推。

(3)直方图对执行计划的影响

如果字段存在倾斜,且也分析了直方图,则在生成执行计划时与没有直方图不同。如果有直方图,会影响计算成本中的选择因子density。在user_tab_columns里有这样的两个列num_distinct和density。在计算基数时,如果没有直方图则基数为num_rows/num_distinct;如果有直方图则为num_rows*density(此时的density<>1/num_distinct)。

下面通过一个案例,说明在有没有直方图的情况下执行计划的不同。

构建的表中,数据严重不均衡,OWNER='HF'的有1条,OWNER='PUBLIC'的有3万多条。


[hf@testdb] SQL> create table t1 as select * from dba_objects;
Table created.

[hf@testdb] SQL> create index idx_owner on t1(owner);
Index created.

[hf@testdb] SQL> select owner,count(*) from t1 group by owner;
OWNER                            COUNT(*)
------------------------------ ----------
OWBSYS_AUDIT                           12
MDSYS                                2011
HF                                      1
PUBLIC                              33996
...
24 rows selected.

收集了一个直方图,这是一个基于频率的直方图。


[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'t1',cascade =>true,method_opt => 'for columns owner size 254');
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select num_distinct,num_buckets,histogram
2  from user_tab_columns
3  where table_name='T1' and column_name='OWNER';
NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------ ----------- ---------------
          24          20 FREQUENCY

对于返回较少数据的情况,例如下面的OWNER='HF'的情况,优化器选择使用了索引扫描。


[hf@testdb] SQL> select * from t1 where owner='HF';
--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |     8 |   784 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID
                           | T1        |     8 |   784 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN | IDX_OWNER |     8 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------

对于返回较多数据的情况,例如下面的OWNER='PUBLIC'的情况,优化器选择使用了全表扫描。这显然是个不错的选择。根据数据分布不同,选择了更为高效的处理方式。


[hf@testdb] SQL> select * from t1 where owner='PUBLIC';
33996 rows selected.
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 33513 |  3207K|   344   (1)| 00:00:05 |
|*  1 |  TABLE ACCESS FULL| T1   | 33513 |  3207K|   344   (1)| 00:00:05 |
--------------------------------------------------------------------------

后面我们去掉了直方图,即忽视了数据的不均衡问题。


[hf@testdb] SQL> exec dbms_stats.gather_table_stats(user,'t1',cascade =>true,method_opt => 'for columns owner size 1');
PL/SQL procedure successfully completed.
[hf@testdb] SQL> select num_distinct,num_buckets,histogram
2  from user_tab_columns
3  where table_name='T1' and column_name='OWNER';
NUM_DISTINCT NUM_BUCKETS HISTOGRAM
------------ ----------- ---------------
          24           1 NONE

在去掉了直方图之后,查询均使用了索引扫描的方式,即使是返回大量数据的owner='PUBLIC'的查询。可见,这并不是一个很好的选择。


[hf@testdb] SQL> alter system flush shared_pool;
System altered.
[hf@testdb] SQL> select * from t1 where owner='HF';
--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |  3595 |   344K|   105   (0)| 00:00:02 |
|   1 |  TABLE ACCESS BY INDEX ROWID
                           | T1        |  3595 |   344K|   105   (0)| 00:00:02 |
|*  2 |   INDEX RANGE SCAN | IDX_OWNER |  3595 |       |     9   (0)| 00:00:01 |
--------------------------------------------------------------------------------

[hf@testdb] SQL> select * from t1 where owner='PUBLIC';
--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |  3595 |   344K|   105   (0)| 00:00:02 |
|   1 |  TABLE ACCESS BY INDEX ROWID
                           | T1        |  3595 |   344K|   105   (0)| 00:00:02 |
|*  2 |   INDEX RANGE SCAN | IDX_OWNER |  3595 |       |     9   (0)| 00:00:01 |
--------------------------------------------------------------------------------

5.扩展统计信息

除了针对表、索引、字段外,从11g开始也提供了针对多列的统计信息。所谓多列的统计信息是指对多个存在关联关系的列(作为一个组合列)收集的统计信息。如果查询语句的WHERE条件中出现这个组合列所涉及的关联列的过滤条件,则优化器在判断时会使用多列统计信息进行估算,而不再使用原始多列组合进行估算。

下面通过一个案例说明统计信息的具体用法。

下面的代码构造了一表t1,并插入了1万条记录。要注意的是,表中字段n1和n2的值完全一样,也就是说这两个字段是有关联的。


[hf@testdb] SQL> create table t1 ( n1 number,n2 number);
Table created.

[hf@testdb] SQL> insert into t1 select 1,trunc(dbms_random.value(0,100)) from dba_objects where rownum<10001;
10000 rows created.

[hf@testdb] SQL> update t1 set n1=n2;
10000 rows updated.

[hf@testdb] SQL> commit;
Commit complete.

[hf@testdb] SQL> exec dbms_stats.gather_table_stats(ownname=>'HF',tabname=>'T1',cascade=>true,estimate_percent=>100);
PL/SQL procedure successfully completed.

执行了一个查询语句,从Rows可见,其评估出的记录数为1。这是因为优化器按照两个字段的选择率相乘作为整体的选择率。因为n1和n2字段都是随机插入的1~100之间的数据,所以单字段的选择率为1/100,组合在一起就是1/10 000。表共有10 000条记录,因此估算选择出的记录数为1条。


[hf@testdb] SQL> select * from t1 where n1=1 and n2=1;
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     6 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |     6 |     7   (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("N1"=1 AND "N2"=1)

由下页的代码可知,实际返回的记录数为94,这与估计的值差异很大。原因就是优化器无法得知n1、n2两个列的值是有关联的。


[hf@testdb] SQL> select count(*) from t1 where n1=1 and n2=1;
COUNT(*)
----------
        94

下面的代码创建了一个扩展统计信息,从视图中可见,这个扩展是包含了(n1,n2)列。


[hf@testdb] SQL> declare
  2    cg_namevarchar2(30);
3  begin
  4    cg_name:=sys.dbms_stats.create_extended_stats('HF','T1','(n1,n2)');
  5    dbms_output.put_line(cg_name);
6  end;
7  /
SYS_STUBZH0IHA7K$KEBJVXO5LOHAS
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select extension_name,extension from dba_stat_extensions where table_name='T1' and owner='HF';
EXTENSION_NAME                 EXTENSION
-------------------------------------------------------------------------------
SYS_STUBZH0IHA7K$KEBJVXO5LOHAS ("N1","N2")

从下面的代码中的Rows输出可见,估算的行数是100,这显然是充分考虑到n1=n2的情况,且与实际的94条记录差异不大。


[hf@testdb] SQL> exec dbms_stats.gather_table_stats(ownname=>'HF',tabname=>'T1',method_opt=>'for columns(n1,n2) size auto',estimate_percent=>100);
PL/SQL procedure successfully completed.

[hf@testdb] SQL> select * from t1 where n1=1 and n2=1;
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   100 |   600 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |   100 |   600 |     7   (0)| 00:00:01 |
--------------------------------------------------------------------------

6.动态采样

随着Oracle数据库逐步淘汰了RBO的优化器方式,CBO成为优化器的唯一选择。而CBO需要依赖于准确的统计信息,如果对象没有收集统计信息,则会造成很大的问题。此时,就需要一种机制避免因为统计信息缺失可能导致的产生低效执行计划的问题。动态采样正是为了帮助优化器获得尽可能多的信息,可以把它视为对象统计信息的必要补充。

一般而言,在下列情况下可能会使用动态采样。

·当表、索引等对象缺乏统计信息的时候,优化器可采用动态采样。

·临时表。一般来说,临时表没有统计信息,多采用动态采样的手段收集。

·对于复杂逻辑,优化器可能无法准确评估,可以采用动态采样。

针对动态采样,可以采用不同的层次。层次越高,其收集的信息越准确,当然其开销也越大。在不同的数据库版本中,其对应的动态采样的默认层次也不一样。如果采样的层次设置为3,则查询优化器通过测量样本中记录的选择性来估算语句中条件的选择性,而不是使用数据字典中的统计信息或者手工设置的值。如果采样的层次设置为4或者更高,则除了层级的操作外,还可以动态采样同一张表在WHERE子句中引用的两个或者更多的字段。当字段间有关系的时候,这将非常有助于提高估算的性能。表4-1所示是动态采样的层次及含义说明。

表4-1 动态采样的层次及含义说明

下面通过一个例子说明动态采样的使用。


[hf@testdb] SQL> create table t1( a int,bvarchar(100));
Table created.

[hf@testdb] SQL> insert into t1 select object_id,object_name from dba_objects;
86282 rows created.

[hf@testdb] SQL> commit;
Commit complete.
*上面创建了一个示例表,然后插入了8万多条记录。[hf@testdb] SQL> create index idx_t1_a on t1(a);
Index created.

[hf@testdb] SQL> select * from t1 where a=100;
--------------------------------------------------------------------------------
| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |          |     1 |    65 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID
                            | T1       |     1 |    65 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN  | IDX_T1_A |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("A"=100)

Note
-----
   - dynamic sampling used for this statement (level=2)

从Note可以看出,这里使用了动态采样,层次是2。

4.1.3 数据字典统计信息

数据字典统计信息是用来描述数据字典基表、索引等的详细信息。这同普通表、索引等没有什么区别。唯一的区别就是管理的方法不同,需要专门的语句进行操作,关于统计信息操作后面会详细说明。

另外有一点需要注意,那就是从10g开始,数据字典统计信息可以自动收集。

4.1.4 内部对象统计信息

内部对象统计信息是用来描述内部表(例如X$系统表)的详细信息。从本质来说,X$表是基于内存数据结构的。如果它的统计信息不准确,会造成低效的执行计划。因为X$本身就是内存结构,低效的执行计划可能会造成访问内存结构所持有的Latch或Mutex长时间得不到释放。如果出现大规模争用的话,数据库会出现CPU使用超高甚至全库挂起的情况。

与普通对象对比,内部对象的统计信息管理方法不同。此外,如果内部对象缺少统计信息,数据库是不会采用动态采样机制的。一般只有在确定是内部对象统计信息不准的情况下,才额外收集它。