SQL优化(二)统计信息

发布于:2024-09-18 ⋅ 阅读:(29) ⋅ 点赞:(0)

收集统计信息 

数据库的统计信息非常重要,如果没有正确地收集表的统计信息,或者没有及时地更新表的统计信息,SQL就有可能走错执行计划,也就会出现性能问题。

统计信息主要分为表的统计信息、列的统计信息、索引的统计信息、系统的统计信息、数据字典的统计信息以及动态性能视图基表的统计信息,我们主要关注表、列、索引的统计信息。

注:本文涉及到的数据库版本为Oracle Database 19c Enterprise Edition Release

表的统计信息 

表的统计信息主要包含表的总行数(num_rows)​、表的块数(blocks)以及行平均长度(avg_row_len)​,通过查询数据字典DBA_TABLES获取表的统计信息。

假设现在我们队FSBZDJ这张表进行统计,可以用以下语句查询统计信息:

 select owner, table_name, num_rows, blocks, avg_row_len
     from dba_tables
     where owner = 'WODEV' --表所属账户
     and table_name = 'FSBZDJ'; --表名

如果是新建的表,可能没有收集过统计信息,有可能查出来是空的,执行下面的存储过程进行收集。 

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(
            ownname             => 'WODEV',--表所属账户
            tabname             => 'FSBZDJ',--表名
            estimate_percent    => 100,--采样率,小表100%即可,超大表可以部分采样
            method_opt          => 'for all columns size auto',--控制直方图收集策略
            no_invalidate       => FALSE,
            degree              => 1,--根据表大小,CPU资源和负载设置
            cascade             => TRUE
);
END;

 

通过查询结果,可以看出该表总数据行数6916行,1016个数据块,平均行长度为777字节。 

列的统计信息

列的统计信息主要包含列的基数、列中的空值数量以及列的数据分布情况(直方图)​。可以通过数据字典DBA_TAB_COL_STATISTICS查看列的统计信息。

可以通过以下语句收集FSBZDJ的列信息:

 select column_name, num_distinct, num_nulls, num_buckets, histogram
    from dba_tab_col_statistics
    where owner = 'WODEV'
    and table_name = 'FSBZDJ';

由于列数过多,只展示部分列。 

NUM_BUCKETS代表直方图的桶数,HISTOGRAM代表直方图类型。

可以用下列语句一起查出基数,选择性等指标。 

select a.column_name,
           b.num_rows,
          a.num_nulls,
          a.num_distinct Cardinality,--基数
          round(a.num_distinct / b.num_rows * 100, 2) selectivity,--选择性
          a.histogram,--直方图类型
          a.num_buckets
      from dba_tab_col_statistics a, dba_tables b
     where a.owner = b.owner
      and a.table_name = b.table_name
      and a.owner = 'WODEV'
      and a.table_name = 'FSBZDJ';

索引的统计信息

集群因子 

集群因子是索引的一个统计属性,用于判断索引回表需要消耗的物理I/O次数

集群因子的计算跟ROWID有关。

可以用以下语句查询FSBZDJ这个表下IDX_FSBZDJ_DJBH索引的集群因子

 select owner, index_name, clustering_factor
     from dba_indexes
     where owner = 'WODEV'
     and index_name = 'IDX_FSBZDJ_DJBH';

 

集群因子的计算需要查出rowid,然后相邻行间作比较 

select * from 
(select fsbzdj_djbh,rowid from fsbzdj order by fsbzdj_djbh)  
where rownum<10

首先我们比较1、2行对应的ROWID是否在同一个数据块,如果在同一个数据块,CLUSTERING_FACTOR+0;如果不在同一个数据块,那么CLUSTERING_FACTOR+1。

然后我们比较2、3对应的ROWID是否在同一个数据块,如果在同一个数据块,CLUSTERING_FACTOR+0;如果不在同一个数据块,那么CLUSTERING_FACTOR+1。

再比较3、4对应的ROWID是否在同一个数据块,如果在同一个数据块,CLUSTERING_FACTOR+0;如果不在同一个数据块,那么CLUSTERING_FACTOR+1。

一直这样有序地比较下去,直到比较完索引中最后一个键值,由此可知

集群因子的值介于表的块数和表行数之间

计算sql如下:

 select sum(case when block#1 = block#2 and file#1 = file#2 then 0
              else 1 end) CLUSTERING_FACTOR
      from (select 
    dbms_rowid.rowid_relative_fno(rowid) file#1,
    lead(dbms_rowid.rowid_relative_fno(rowid), 1, null) over(order by fsbzdj_djbh) file#2,
    dbms_rowid.rowid_block_number(rowid) block#1,
    lead(dbms_rowid.rowid_block_number(rowid), 1, null) over(order by fsbzdj_djbh) block#2
    
    from fsbzdj
    where fsbzdj_djbh is not null
    );

计算结果与统计的结果接近,不是完全相等因为表的统计信息没有实时更新。

如果集群因子与块数接近,说明表的数据基本上是有序的,而且其顺序基本与索引顺序一样。这样在进行索引范围或者索引全扫描的时候,回表只需要读取少量的数据块就能完成。

如果集群因子与表记录数接近,说明表的数据和索引顺序差异很大,在进行索引范围扫描或者索引全扫描的时候,回表会读取更多的数据块。 

集群因子只会影响索引范围扫描(INDEX RANGE SCAN)以及索引全扫描(INDEX FULL SCAN)​,因为只有这两种索引扫描方式会有大量数据回表。

FSBZDJ这个表块数是1016个块,表行数为6916,集群因子为5184,更接近表的行数 ,说明表的数据和索引顺序差异很大,如果SQL执行中发生回表,将会有严重的性能影响。

索引的统计信息 

索引的统计信息主要包含索引blevel(索引高度-1)​、叶子块的个数(leaf_blocks)以及集群因子(clustering_factor)​。可以通过数据字典DBA_INDEXES查看索引的统计信息。

创建索引的时候会自动收集索引的统计信息,可以用以下SQL收集统计信息。

--查询某个索引
select  blevel, leaf_blocks, clustering_factor,status
     from dba_indexes
     where owner = 'WODEV'
     and index_name = 'IDX_FSBZDJ_DJBH';


--单独收集某个索引的统计信息
BEGIN
      DBMS_STATS.GATHER_INDEX_STATS(ownname => 'WODEV',
                                    indname => 'IDX_FSBZDJ_DJBH');
END;

 

统计用的数据库函数

DBMS_STATS.GATHER_TABLE_STATS

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

ownname代表表的所属账户,tabname代表表名。

estimate_percent代表采样率,范围是0.000001~100。

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

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

采样率不要低于30%。查看某个表采样率用以下SQL:

SELECT owner,
       table_name,
       num_rows,
       sample_size,
       round(sample_size / num_rows * 100) estimate_percent --采样率
     FROM DBA_TAB_STATISTICS
    WHERE owner='WODEV' AND table_name='FSBZDJ';

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

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

='for all columns size skewonly'  表示对表中所有列收集自动判断是否收集直方图,实际很少使用。

='for all columns size auto'(参数默认值) 表示对出现在where条件中的列自动判断是否收集直方图。

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

='for columns 列名 size skewonly' 表示单独对该列收集直方图,其余列之前如果收集过直方图,本次也继续收集直方图。

no_invalidate表示共享池中涉及到该表的游标是否立即失效,默认值为DBMS_STATS.AUTO_INVALIDATE,表示让Oracle自己决定是否立即失效。建议将no_invalidate参数设置为false,立即失效。

degree表示收集统计信息的并行度,默认为NULL。如果表没有设置degree,收集统计信息的时候后就不开并行;如果表设置了degree,收集统计信息的时候就按照表的degree来开并行。可以查询DBA_TABLES.degree来查看表的degree,一般情况下,表的degree都为1。

granularity表示收集统计信息的粒度,该选项只对分区表生效,默认为AUTO,表示让Oracle根据表的分区类型自己判断如何收集分区表的统计信息。数据库默认采用AUTO方式。

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

查看表的统计信息是否过期

如果表中有大量数据发生变化,这时表的统计信息就过期了,需要重新收集表的统计信息,如果不重新收集,可能会导致执行计划走错。Oracle数据库中,表中只要有超过10%的数据发生变化,统计信息就会过期。

查询统计信息是否过期:

 begin
    dbms_stats.flush_database_monitoring_info;--刷新数据库监控信息
 end;

 select owner, table_name , object_type, stale_stats, last_analyzed
     from dba_tab_statistics
     where owner = 'WODEV'
     and table_name = 'FSBZDJ';

STALE_STATS为NO代表没过期,如果是YES代表过期。

如果过期了需要重新收集统计信息。


网站公告

今日签到

点亮在社区的每一天
去签到