--====================
-- Oracle histogram
--====================
直方图意义:在oracle数据库中,CBO会默认认为目标列的数据量在其最小值和最大值之间是均匀分布的(最小值最大值不准确会导致谓词越界),并且会按照这个均匀分布原则来计算对目标列事假的where查询条件后的可选这率及结果集的cardinality,进而据此来计算成本值并选择执行计划。但是,目标列的数据是均匀分布的按照这个原则选择执行计划是正确的;如果目标数据列分布不均匀,甚至是严重倾斜,分布极度不均匀,那么这个按照这个原则选择执行计划就不合适,甚至是错误的,为此我们需要对那些数据分布不均匀的列进行直方图收集。
直方图是一种列的特殊的统计信息,主要用来描述列上的数据分布情况。当数据分布严重倾斜时,直方图可以用小的提升cardinality评估的准确度。构造直方图最主要的原因就是帮助优化器在表中数据验证倾斜是做出最好的选择。例如,表中的某个列上数据占据了整个表的80%(数据分布倾斜),相关的索引就可能无法帮助减少满足查询所需的I/O数量。创建直方图可以让基于成本的优化器知道何时使用索引才最合适。
直方图实际存储在数据字典sys.histgrm$中,可以通过数据字典dba_tab_historgrams,dba_part_histograms和dba_subpart_histograms来分别查看表,分区表的分区和分区表的子分区的直方图信息。
直方图使用场景:
(1) 当where字句引用了列值分布存在倾斜时。当这种偏差相当明显时,以至于where字句中的值将会使优化器选择不同的执行计划。这时应该使用直方图来帮助优化器来修正执行路径。(如查询不使用该列,则在该列上创建直方图也没有意义)
(2)当列值导致不正确的判断时,这种情况通常发生在多表链接时。当多表链接查询时,oracle会自动判断哪些结果集作为驱动表,如果倾斜列上有直方图可以帮助优化器做出正确的选择。
直方图分类:
Oracle数据库里的直方图使用了一种称为Bucket(桶)的方式来描述目标列的数据分布。bucket是一个逻辑上的概念,相当于分组,每个bucket就是一组,每个bucket里会存储一个或多个目标的数据。oracle会用两个维度来描述一个bucket,这两个维度是endpoint_number和endpoint_value,oracle会将每个bucket的这两个维度记录在数据字典基表sys.histgrm$中。列的直方图类型可以通过查询dba_tab_col_statistics的histogram列来获取,一般情况下包含3类,NONE,FREQUENCY(频率直方图,也叫等频直方图),height balanced(高度平衡直方图,也叫等高直方图)。在12c中 新添了顶级频率直方图(Top Frequency Histogram)和 混合直方图(Hybrid Histogram)。
(1)频率直方图(Frequency,Freq):
在oracle 12c之前,在目标列的数据分布是倾斜的情况下(即存储在数据字典里的目标的distinct值得数量小于目标表的记录数),如果存储在数据字典里描述目标直方图的bucket数量等于目标列的distinct值得数量,那么这种类型的直方图就是频率(frequency)直方图。
频率直方图只适用于目标列的distinct值小于或者等于254的情形。在12c中,直方图的bucket数量可以大于254。对于频率直方图而言,目标列直方图的bucket数量就等于列的distinct数值,此时目标咧有多少distinct值,oracle在数据字典dba_tab_histograms,dba_part_histograns,dba_subpart_histograms(分别对应表,分区别,子分区表直方图信息)中就会存储多少条信息,每一条记录就代表了对其中的一个bucket的描述,上述数据字典中的字段endpoint_value记录了这些distinct值,而字段endpoint_number则记录了到此distinct值为止总共有多少条记录。需要注意的是,对频率直方图而言,endpoint_number是一个累加值,可以使用一条记录的endpoint_number 值减去它上一条记录的endpoint_number值来得到这条记录本身所对应的endpoint_value值记录数,
select deptno,count(1),sum(count(1)) over(order by deptno range unbounded preceding) idg from dept group by deptno;
SQL> select table_name,column_name,endpoint_number,endpoint_value,nvl((endpoint_number-(lag(endpoint_number) over (orderby endpoint_value))),endpoint_number) counts from dba_tab_histograms where table_Name='DEPT';
TABLE_NAME COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE COUNTS---------- ---------- --------------- -------------- ----------DEPT DEPTNO 0 10 0DEPT DEPTNO 1 40 1DEPT DNAME 0 3.3886E+35 -1DEPT LOC 0 3.4430E+35 0DEPT LOC 1 4.0641E+35 1DEPT DNAME 1 4.3229E+35 0已选择6行。
关于直方图的一些注意事项:
(1)对于超过32个字符的列,超出的部分无法在直方图中收集,这个缺陷会影响CBO优化器对结果集的评估。
(2)数字和日期在直方图上被精确表示。
(3)如果目标列的数据是均衡分布的(主键列,唯一索引),那么就不需要对相关列收集直方图。
(4)对于那些没有在where 中出现的列,对其收集直方图统计信息没有任何用途。oracle只会对那些在谓词中使用过的列收集直方图。sys.col_usage$中记录相关列的使用记录( INTCOL#记录),如果没有相关记录则目标列没有使用过,就不会对其收集直方图。
(5)如果某个列在where条件中从未出现,那么在sys.col_usage$中就不会有这个列的使用记录,那么数据库默认就不会去收集这个列的直方图信息
(6)如果目标的distinct值得数量跟目标表的记录数量相同(数据不倾斜),即使该目标列在sys.col_usage$中有使用记录,那么oracle默认也不会去收集该列的直方图统计信息。
(7)oracle是怎么来判断某个列的数据是否倾斜呢?oracle采用了一种很简单的方法来判断某列是否倾斜,就是判断该列的DISTINCT值得数量是否很表记录数是否相同,相差较大就是严重倾斜。
(8)如果目标列的Distinct值的数量跟目标表记录是相同(数据不倾斜),即使该列在sys.col_uage$中有使用记录,那么oracle默认也不会收集次列的直方图信息。
(9)“在手工收集直方图统计的时候,如果手动指定buclet的数据量等于目标咧的distinct值的数量,且这个值小于254的话,那么oracle收集的直方图信息的类型就是频率直方图(Frequence)”。
(10)在12c之前的版本,对于频率直方图,目标列的distinct值不能大于254。
(11)需要注意的是 直方图收集会影响cursor sharing的共享。
模拟环境:
SQL> select name,count(1) from t1 group by name;
NAME COUNT(1)
-------------------- ----------
1 10000
2 1
删除列直方图:
exec dbms_stats.gather_table_stats('plat2','t1',method_opt=>'for solumns name size 1',cascade=>true);
删除表直方图信息
SQL> exec dbms_stats.delete_table_stats('PLAT2','T1');
PL/SQL 过程已成功完成。
SQL> select table_name,column_name,endpoint_number,endpoint_value from dba_tab_histograms where table_name='T1';
未选定行
SQL> select column_name,num_distinct,num_nulls,num_buckets,histogram from dba_tab_col_statistics where table_name='T1';
未选定行
列对象使用情况
SQL> select INTCOL#,EQUALITY_PREDS,obj# from sys.col_usage$ where obj#=74602;
INTCOL# EQUALITY_PREDS OBJ#
---------- -------------- ----------
2 3 74602
收集列对象直方图
SQL> exec dbms_stats.gather_table_stats('PLAT2','T1',method_opt=>'for columns name size auto',cascade=>true);
PL/SQL 过程已成功完成。
使用列对象
SQL> select * from t1 where name=2;
ID NAME
---------- --------------------
2 2
列对象使用情况
SQL> select INTCOL#,EQUALITY_PREDS,obj# from sys.col_usage$ where obj#=74602;
INTCOL# EQUALITY_PREDS OBJ#
---------- -------------- ----------
2 4 74602
查询列对象直方图信息
SQL> select column_name,num_distinct,num_nulls,num_buckets,histogram from dba_tab_col_statistics where table_name='T1';
COLUMN_NAM NUM_DISTINCT NUM_NULLS NUM_BUCKETS HISTOGRAM
---------- ------------ ---------- ----------- ------------------------------
NAME 2 0 2 FREQUENCY
以上可见,直方图收集起了作用,而且收集的直方图列对象必须在谓词where中使用过才能收集。
SQL> select table_name,column_name,endpoint_number,endpoint_value from dba_tab_histograms where table_name='T1
TABLE_NAME COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE
---------- ---------- --------------- --------------
T1 NAME 10001 2.5961E+35
T1 NAME 10000 2.5442E+35
(2)高度平衡直方图(Height Balanced,HtBal)
如果存储在数据字典描述目标列直方图的bucket的数量小于目标列的distinct值的数量,这种类型的直方图就称之为高度平衡直方图(Height Balanced)。当distinct值大于254,那么只能使用高度平衡直方图。
如果目标列distinct值大于254,收集直方图的时候会自动转化成平衡直方图。
在dba_tab_histograms视图中,endpoint_number代表桶,qie自动省略去endpoint_value值相同切endpoint_number相邻的桶的值。
endpoint_value表示每一个桶的最大值,
SQL> select column_name,num_distinct,num_nulls,num_buckets,histogram from dba_tab_col_statistics where table_name='T3';
COLUMN_NAM NUM_DISTINCT NUM_NULLS NUM_BUCKETS HISTOGRAM
---------- ------------ ---------- ----------- ------------------------------
ID 2000 0 254 HEIGHT BALANCED
收集直方图信息:
默认情况下,数据库会为实例收集基本的统计信息,但是不会收集直方图信息。
通常使用 dbms_stats包method_opt参数来创建直方图。
method_opt参数:
for all [indexd | hidden ] columns [size_number]
for columns [size_number] column|attribute [szie_number] [,column|attrobute [size_number].....]
其中size_num 必须符合一下格式:
skewonly :只对数据分布不均衡的列收集直方图统计信息
repeat(重复):只对已经存在的直方图统计信息的列收集直方图
auto :自行决定对那些列收集直方图统计信息
integer:直方图的bucket数量,在1-254范围内,1表示删除该目标上的直方图统计信息
method_opt默认参数是 " for all columns size auto"," for all columns size 1"表示删除所有列的直方图统计信息。
如下是一些常用收集方式:
for all indexed columns size auto:对表所有索引的列自动收集直方图统计信息
for columns size auto a b:对表的列a列b自动收集直方图统计信息
for columns size 10 a b :对表列 a列b 收集直方图统计信息,同时制定bucket为10
for columns a size 10 b size 5 :对表列 a列b 收集直方图统计信息,指定列a bucket位10,列b bucket为5
for columns a size 1:删除列a的直方图统计信息
dbms_stats.delete_columns_stats('SH','SALES','sal_id',col_stat_type=>'histogram');删除表列直方图统计信息,其中col_stat_type默认值为ALL。
复合直方图:
收集表外部信息:
由于oracle系统默认表中各列之间的关系不变。但是日常工作中,收集多列的直方图信息,各列的关系会随着条件改变而改变,创建扩展信息就是收集指定列之前的关系,从而判断出结果集倾斜的结果,做出正确的判断。
select dbms_stats.create_extended_stats('sh’,‘SALES','(cust_city,cust_state_province,country_id)') from dual;
收集列直方图:
exec dbms_stats.gather_table_stats('hr','sales',method_opt=>'for columns (cust_city,cust_state_province,country_id) size auto )';
收集表统计信息:
exec dbms_stats.gather_table_stats(’sh',‘sales',cascade=>true);
查询列直方案图信息:
select column_name,num_buckets,histogram from dba_tab_col_statistics where table_name='T1' ;
直方图实例:
create table t1 (id number,name varchar2(10));
SQL> declare
cnt number(5) :=1;
begin
loop
insert into t1 values(1,1);
if cnt=10000 then
exit;
end if;
cnt :=cnt+1;
end loop;
insert into t1 values(2,2);
commit;
end;
/
数据分布:
SQL> select name,count(1) from t1 group by name;
NAME COUNT(1)
-------------------- ----------
1 10000
2 1
SQL> select sql_id,sql_text from v$sql where sql_text like '%t1%';
SQL_ID
--------------------------
SQL_TEXT
--------------------------------------------------------------------------------
4jt9tpc59uqgq
select * from t1 where name='2'
SQL> select * from table(dbms_xplan.display_cursor('4jt9tpc59uqgq',0,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID 4jt9tpc59uqgq, child number 0
-------------------------------------
select name,count(1) from t1 group by name
Plan hash value: 136660032
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 7 (100)| |
|* 1 | TABLE ACCESS FULL| T1 | 5001 | 25005 | 7 (0)| 00:00:01 |
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
2 - SEL$1 / T1@SEL$1
Outline Data
-------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "T1"@"SEL$1")
USE_HASH_AGGREGATION(@"SEL$1")
END_OUTLINE_DATA
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
*/
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - "NAME"[VARCHAR2,10], COUNT(*)[22]
2 - "NAME"[VARCHAR2,10]
已选择41行。
从执行计划可以看到,走了全表扫描,这显然是不正确的,‘name=2’只有一条记录,此时走索引扫描最高效。由于CBO在分析的时候,name列只有2个值,所以选择率10001的1/2,也就是5001超过了总数一半,所以此时oracle认为返回数据超过了总数一半,走全表扫描最有效。其实,实际上,对name列的分布严重不均匀,评估严重失误,从以上执行计划可以走全表扫描返回基数5001,其实应该是1,这个时候我们可以对列name收集直方图,来让CBO统计的时候可以读取列分布情况。
SQL> exec dbms_stats.gather_table_stats('PLAT2','T1',method_opt=>'for columns name size auto',cascade=>true);
SQL> select * from table(dbms_xplan.display_cursor(null,0,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID ab1q9qzvfj12d, child number 0
-------------------------------------
select * from t1 where name=2
Plan hash value: 3617692013
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 7 (100)| |
|* 1 | TABLE ACCESS FULL| T1 | 1 | 5 | 7 (0)| 00:00:01 |
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1 / T1@SEL$1
Outline Data
-------------
/*+
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "T1"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
---------------------------------------------------
1 - filter(TO_NUMBER("NAME")=2)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - "T1"."ID"[NUMBER,22], "NAME"[VARCHAR2,10]
已选择42行。
以上可见,收集直方图之后,执行计划按照预想方式执行。
系统会默认采集直方图信息,为了保持系统稳定性我们建议对已存在的直方图信息才收集,其余的手动收集。
查看系统默直方图收集范围,修改为对已经存在的直方图收集信息:
SQL> select dbms_stats.get_param('method_opt') from dual;
DBMS_STATS.GET_PARAM('METHOD_OPT')
--------------------------------------------------------------------------------
FOR ALL COLUMNS SIZE AUTO
修改成对已存在的直方图收集
SQL> exec dbms_stats.set_param('method_opt','for all columns size repeat');
PL/SQL procedure successfully completed.
SQL> select dbms_stats.get_param('method_opt') from dual;
DBMS_STATS.GET_PARAM('METHOD_OPT')
--------------------------------------------------------------------------------
FOR ALL COLUMNS SIZE REPEAT
注意:
使用绑定变量会导致直方图统计信息被忽略。