其他
你知道吗?其实 Oracle 直方图自动统计算法存在这些缺陷!(附验证步骤)
【实验环境】
【10053事件常用信息说明】
实验步骤
1.测试表中只有单月的varchar2数据类型情况
1.1 创建相应的表和模拟数据,此处我们先使用varchar2类型
( "DAY_FLWNO" VARCHAR2(25) NOT NULL ENABLE,
"DAY_TRNTIME" VARCHAR2(30) NOT NULL ENABLE,
"DAY_CSTNO" VARCHAR2(16),
"DAY_STDBSNCOD" VARCHAR2(30),
"DAY_CSTACC" VARCHAR2(60),
"DAY_ACCTYP" VARCHAR2(3),
"DAY_ACCCRY" VARCHAR2(3),
"DAY_TRNAMT" NUMBER(15,2),
"DAY_CHANNEL" VARCHAR2(20),
"DAY_TRNCOUNT" NUMBER) ;
create table t1 as select * from dba_objects;
1.2模拟数据,此时只模拟单月数据,数据不跨月,因为若数据跨月的话,oracle在转换成raw类型的时候,会产生两个internal value
select rownum , to_char(to_date('20190901','yyyymmdd')+round(dbms_random.value(0 ,86400*3-1))/86400,'yyyymmddhh24miss') ,round(dbms_random.value(0,2000000))
from t1 where rownum<=5265655;
commit;
1.3创建相应的索引
CREATE INDEX "INDEX_DAY_TRNFLW_C" ON "DAY_TRNFLW" ("DAY_TRNTIME");
1.4收集统计信息并确认此时没有自动收集直方图
col HISTOGRAM format a10
select column_name,histogram,low_value,high_value
from dba_tab_columns
where table_name='DAY_TRNFLW'
and column_name='DAY_TRNTIME' ;
COLUMN_NAME HISTOGRAM
------------------------------ ----------
LOW_VALUE
--------------------------------------------------------------------------------
HIGH_VALUE
--------------------------------------------------------------------------------
DAY_TRNTIME NONE
3230313930393031303030303131
3230313930393033323335393537
1.5 下面使用10053事件跟踪CBO,确认无直方图时是否会判断谓词越界
select spid,pid from v$process a ,v$session b where a.addr=b.paddr AND b.sid=&sid;
SID
----------
3515
SQL> SQL> Enter value for sid: 3515
old 1: select spid,pid from v$process a ,v$session b where a.addr=b.paddr AND b.sid=&sid
new 1: select spid,pid from v$process a ,v$session b where a.addr=b.paddr AND b.sid=3515
SPID PID
------------------------ ----------
28366 157
1.6新开启一个会话,使用oradebug对session 3515进行trace
Oracle pid: 157, Unix process pid: 28366, image: oracle@hqxtsl-oracle-a01 (TNS V1-V3)
SQL> oradebug event 10053 trace name context forever,level 2;
var p1 varchar2(30);
var p2 varchar2(30);
SQL> SQL> SQL> exec :p0:='12345';
exec :p1:='20190721';
exec :p2:='20190722'; --由于表中都是9月份数据,所以此处变量p2超出了列DAY_TRNTIME的取值范围
select SUM(nvl(DAY_TRNAMT,1)) as "sumAmt" , SUM(nvl(DAY_TRNCOUNT,1)) as "trnCount" from DAY_TRNFLW
where DAY_CSTNO = :p0
and DAY_TRNTIME between :p1 and :p2;
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL> 2 3
sumAmt trnCount
---------- ----------
1.7结束10053跟踪,并找出相应的trace文件
Statement processed.
SQL>oradebug event 10053 trace name context off
/oracle/app/oracle/diag/rdbms/dbcon/dbcon1/trace/dbcon1_ora_28366.trc
Bind#0
oacdty=01 mxl=128(90) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=01 csi=873 siz=384 off=0
kxsbbbfp=7fa6f5003ce8 bln=128 avl=05 flg=05
value="12345"
Bind#1
oacdty=01 mxl=128(90) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=01 csi=873 siz=0 off=128
kxsbbbfp=7fa6f5003d68 bln=128 avl=08 flg=01
value="20190721"
Bind#2
oacdty=01 mxl=128(90) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=01 csi=873 siz=0 off=256
kxsbbbfp=7fa6f5003de8 bln=128 avl=08 flg=01
value="20190722"
Access path analysis for DAY_TRNFLW
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for DAY_TRNFLW[DAY_TRNFLW]
SPD: Return code in qosdDSDirSetup: NOCTX, estType = TABLE
Column (#3): DAY_CSTNO(VARCHAR2)
AvgLen: 8 NDV: 70976 Nulls: 0 Density: 0.000014
Estimated selectivity: 1.4089e-05 , col: #3
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_CSTNO"=:B1
Estimated selectivity: 1.4089e-05 , col: #3
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_TRNTIME">=:B1
Column (#2): DAY_TRNTIME(VARCHAR2) --此处数据类型是varchar2
AvgLen: 15 NDV: 63912 Nulls: 0 Density: 0.000016
Estimated selectivity: 1.000000 , col: #2 --说明:该处的选择率为1是正确的,因为表中所有的行都满足大于绑定变量的值’20190721’,基数即为所有的行
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_TRNTIME"<=:B1
Using density: 1.5647e-05 of col #2 as selectivity of unpopular value pred --因为从谓词中得不到合适的选择率,此处直接使用上面的密度,这里可以看到,虽然传递的绑定变量值’20190722’都小于列值(9月份数据),CBO并没有判断发生了谓词越界
Table: DAY_TRNFLW Alias: DAY_TRNFLW
Card: Original: 73269.000000 Rounded: 1 Computed: 0.000016 Non Adjusted: 0.000016 --使用该密度的情况下,输出值认为是1
Scan IO Cost (Disk) = 102.000000
Scan CPU Cost (Disk) = 16556182.800000
.
.
.
Best:: AccessPath: IndexRange
Index: INDEX_DAY_TRNFLW_A --CBO选择了正确的索引A
Cost: 3.000594 Degree: 1 Resp: 3.000594 Card: 0.000016 Bytes: 0.000000
OBJ# INTCOL# EQUALITY_PREDS EQUIJOIN_PREDS NONEQUIJOIN_PREDS RANGE_PREDS LIKE_PREDS NULL_PREDS TIMESTAMP FLAGS
---------- ---------- -------------- -------------- ----------------- ----------- ---------- ---------- --------- ----------
114538 2 0 0 0 3 0 0 20-FEB-20 8
114538 3 3 0 0 0 0 0 20-FEB-20 513
1.9进行统计信息搜集
PL/SQL procedure successfully completed.
1.10确认直方图信息
col HISTOGRAM format a10
select column_name,histogram,low_value,high_value
from dba_tab_columns
where table_name='DAY_TRNFLW'
and column_name='DAY_TRNTIME' ;SQL> SQL> 2 3 4
COLUMN_NAME HISTOGRAM
------------------------------ ----------
LOW_VALUE
--------------------------------------------------------------------------------
HIGH_VALUE
--------------------------------------------------------------------------------
DAY_TRNTIME NONE
3230313930393031303030303032
3230313930393033323335393532
测试表中含有多月(8月和9月)的数据进行
2.1将表中数据更新为8月份,模拟多月数据
commit;
2.2收集统计信息
col HISTOGRAM format a10
select column_name,histogram,low_value,high_value
from dba_tab_columns
where table_name='DAY_TRNFLW'
and column_name='DAY_TRNTIME' ;SQL> SQL> 2 3 4
COLUMN_NAME HISTOGRAM
------------------------------ ----------
LOW_VALUE
--------------------------------------------------------------------------------
HIGH_VALUE
--------------------------------------------------------------------------------
DAY_TRNTIME HYBRID
3230313930383239303030303032
3230313930393033323335393532
2.3从share pool中清理现有sql执行计划,以便重新硬解析该sql(10053事件只能跟踪硬解析,不能跟踪软解析和软软解析)
Enter value for sql_id: azf5wm5qhptmy
old 1: select sql_id,address,hash_value,PLAN_HASH_VALUE from v$sql where sql_id='&sql_id'
new 1: select sql_id,address,hash_value,PLAN_HASH_VALUE from v$sql where sql_id='azf5wm5qhptmy'
SQL_ID ADDRESS HASH_VALUE PLAN_HASH_VALUE
------------- ---------------- ---------- ---------------
azf5wm5qhptmy 00000000FF897F90 1829430910 2119561882
SQL> BEGIN
DBMS_SHARED_POOL.PURGE('&address,&hash_value', 'C');
END;
/ 2 3 4
Enter value for address: 00000000FF897F90
Enter value for hash_value: 1829430910
old 2: DBMS_SHARED_POOL.PURGE('&address,&hash_value', 'C');
new 2: DBMS_SHARED_POOL.PURGE('00000000FF897F90,1829430910', 'C');
PL/SQL procedure successfully completed.
SQL> select sql_id,address,hash_value,PLAN_HASH_VALUE from v$sql where sql_id='azf5wm5qhptmy';
no rows selected
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for DAY_TRNFLW[DAY_TRNFLW]
SPD: Return code in qosdDSDirSetup: NOCTX, estType = TABLE
Column (#3): DAY_CSTNO(VARCHAR2)
AvgLen: 8 NDV: 70976 Nulls: 0 Density: 0.000014
Estimated selectivity: 1.4089e-05 , col: #3
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_CSTNO"=:B1
Estimated selectivity: 1.4089e-05 , col: #3
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_TRNTIME">=:B1
Column (#2):
NewDensity:0.000015, OldDensity:0.000015 BktCnt:5371.000000, PopBktCnt:0.000000, PopValCnt:0, NDV:65096
Column (#2): DAY_TRNTIME(VARCHAR2)
AvgLen: 15 NDV: 65096 Nulls: 0 Density: 0.000015
Histogram: Hybrid #Bkts: 254 UncompBkts: 5371 EndPtVals: 254 ActualVal: yes
Estimated selectivity: 1.000000 , col: #2 --此处CBO评估出来选择率还是1,因为所有行都满足大于’20190721’的条件,但是在收集直方图统计信息的时候,桶数为254,因此收集统计信息的时候是可以探测到该列上是存在大于或等于254个唯一值的,且有5371个被压缩的buckets
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_TRNTIME"<=:B1
Using prorated density: 6.8242e-06 of col #2 as selectivity of out-of-range/non-existent value pred --此处CBO根据直方图判断出发生了谓词越界
.
.
.
Access Path: index (RangeScan)
Index: INDEX_DAY_TRNFLW_A
resc_io: 4.000000 resc_cpu: 29216 --CBO评估出走索引A的开销为4
ix_sel: 1.4089e-05 ix_sel_with_filters: 9.6148e-11
Cost: 4.000785 Resp: 4.000785 Degree: 1
****** Costing Index INDEX_DAY_TRNFLW_C
SPD: Return code in qosdDSDirSetup: NOCTX, estType = INDEX_SCAN
SPD: Return code in qosdDSDirSetup: NOCTX, estType = INDEX_FILTER
Estimated selectivity: 1.000000 , col: #2
Using prorated density: 6.8242e-06 of col #2 as selectivity of out-of-range/non-existent value pred
Access Path: index (RangeScan)
Index: INDEX_DAY_TRNFLW_C
resc_io: 3.000000 resc_cpu: 21919 --CBO通过索引C可以直接通过谓词越界过滤掉所有的行,评估出走索引C的开销为3,低于索引A
ix_sel: 6.8242e-06 ix_sel_with_filters: 6.8242e-06
Cost: 3.000588 Resp: 3.000588 Degree: 1
Used INDEX_DAY_TRNFLW_C
Cost = 3.000393, sel = 1.5362e-05
Not used INDEX_DAY_TRNFLW_A
Cost = 4.000590, sel = 1.4089e-05
.
.
.
****** finished trying bitmap/domain indexes ******
Best:: AccessPath: IndexRange
Index: INDEX_DAY_TRNFLW_C --最终,CBO通过比较开销,选择了索引C,SQL的执行计划发生了相应变化
Cost: 3.000588 Degree: 1 Resp: 3.000588 Card: 0.000016 Bytes: 0.000000
至此,我们可以得出如下结论:
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(128)
TABLE_NAME NOT NULL VARCHAR2(128)
COLUMN_NAME NOT NULL VARCHAR2(128)
.
.
.
LOW_VALUE RAW(2000)
HIGH_VALUE RAW(2000)
RAW转换模拟测试
UTL_RAW.CAST_TO_RAW('DAY_TRNTIME')
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
4441595F54524E54494D45
SQL> select utl_raw.cast_to_raw('DAY_TRNTIME') raw_for_cbo, count(1) from DAY_TRNFLW group by 1;
RAW_FOR_CBO
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
COUNT(1)
----------
4441595F54524E54494D45
73269
.
.
.
20190901200645 4
20190901200958 4
20190901213042 4
20190901224944 4
20190902005954 4
20190902092241 4
20190902102315 4
20190902113652 4
20190902135131 4
20190902151930 4
20190902172156 4
DAY_TRNTIME NUM_ROWS
------------------------------ ----------
20190902173951 4
20190902182755 4
20190902201506 4
20190903001538 4
20190903033351 4
20190903040014 4
20190903180612 4
20190902212139 5
65887 rows selected.
Date数据类型测试
4.1创建date型数据表
Table dropped.
SQL> CREATE TABLE "DAY_TRNFLW"
( "DAY_FLWNO" VARCHAR2(25) NOT NULL ENABLE,
"DAY_TRNTIME" date NOT NULL ENABLE,
2 3 "DAY_CSTNO" VARCHAR2(16),
"DAY_STDBSNCOD" VARCHAR2(30),
4 5 "DAY_CSTACC" VARCHAR2(60),
6 7 "DAY_ACCTYP" VARCHAR2(3),
"DAY_ACCCRY" VARCHAR2(3),
"DAY_TRNAMT" NUMBER(15,2),
"DAY_CHANNEL" VARCHAR2(20),
"DAY_TRNCOUNT" NUMBER); 8 9 10 11
Table created.
insert into DAY_TRNFLW (DAY_FLWNO,DAY_TRNTIME,DAY_CSTNO)
select rownum , to_date('20190901','yyyymmdd')+round(dbms_random.value(0,86400*3-1))/86400 ,round(dbms_random.value(0,2000000))
from t1,t1 where rownum<=5265655;
commit;
4.2模拟表中同时存在8月和9月的数据
Commit;
4.3确认col_usage$是否有谓词使用记录
OBJECT_ID
----------
114546
SQL> select * from sys.col_usage$ where obj#=114546;
no rows selected.
4.4执行一下sql,以便oracle可以记录该谓词使用记录
var p1 varchar2(30);
var p2 varchar2(30);
exec :p0:='12345';
exec :p1:=to_date('20190721','yyyymmdd');
exec :p2:=to_date('20190722','yyyymmdd');
select SUM(nvl(DAY_TRNAMT,1)) as "sumAmt" , SUM(nvl(DAY_TRNCOUNT,1)) as "trSQL> nCount" from DAY_TRNFLW where
DAY_CSTNO = :p0 and
DAY_TRNTIME between :p1 and :p2;SQL> SQL>
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL> SQL> 2 3
sumAmt trnCount
---------- ----------
SQL> select * from sys.col_usage$ where obj#=114546;
OBJ# INTCOL# EQUALITY_PREDS EQUIJOIN_PREDS NONEQUIJOIN_PREDS
---------- ---------- -------------- -------------- -----------------
RANGE_PREDS LIKE_PREDS NULL_PREDS TIMESTAMP FLAGS
----------- ---------- ---------- --------- ----------
114546 2 0 0 0
1 0 0 20-FEB-20 8
114546 3 1 0 0
0 0 0 20-FEB-20 513
4.5收集统计信息
4.6清理原有执行计划
System altered.
4.7进行date类型的10053跟踪
select spid,pid from v$process a ,v$session b where a.addr=b.paddr AND b.sid=&sid;
SID
----------
2664
SQL> SQL> Enter value for sid: 2664
old 1: select spid,pid from v$process a ,v$session b where a.addr=b.paddr AND b.sid=&sid
new 1: select spid,pid from v$process a ,v$session b where a.addr=b.paddr AND b.sid=2664
SPID PID
------------------------ ----------
18094 150
SQL> ORADEBUG SETORAPID 150;
Oracle pid: 150, Unix process pid: 18094, image: oracle@hqxtsl-oracle-a01 (TNS V1-V3)
SQL> oradebug event 10053 trace name context forever,level 2;
Statement processed.
var p1 varchar2(30);
var p2 varchar2(30);
SQL> SQL> SQL> exec :p0:='12345';
exec :p1:=to_date('20190721','yyyymmdd');
exec :p2:=to_date('20190722','yyyymmdd');
select SUM(nvl(DAY_TRNAMT,1)) as "sumAmt" , SUM(nvl(DAY_TRNCOUNT,1)) as "trnCount" from DAY_TRNFLW where
DAY_CSTNO = :p0 and
DAY_TRNTIME between :p1 and :p2;
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL> SQL> 2 3
sumAmt trnCount
---------- ----------
Statement processed.
SQL>oradebug event 10053 trace name context off;
/oracle/app/oracle/diag/rdbms/dbcon/dbcon1/trace/dbcon1_ora_18094.trc
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for DAY_TRNFLW[DAY_TRNFLW]
SPD: Return code in qosdDSDirSetup: NOCTX, estType = TABLE
Column (#3): DAY_CSTNO(VARCHAR2)
AvgLen: 8 NDV: 1866880 Nulls: 0 Density: 0.000001
Estimated selectivity: 5.3565e-07 , col: #3
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_CSTNO"=:B1
Estimated selectivity: 5.3565e-07 , col: #3
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_TRNTIME">=:B1
Column (#2):
NewDensity:0.000004, OldDensity:0.000004 BktCnt:5495.000000, PopBktCnt:0.000000, PopValCnt:0, NDV:272032
Column (#2): DAY_TRNTIME(DATE)
AvgLen: 8 NDV: 272032 Nulls: 0 Density: 0.000004 Min: 2458725.000243 Max: 2458730.999988
Histogram: Hybrid #Bkts: 254 UncompBkts: 5495 EndPtVals: 254 ActualVal: no --换成date后,此处的选择率同样为1,并且同样收集了直方图
Estimated selectivity: 1.000000 , col: #2
kkecdn: Single Table Predicate:"DAY_TRNFLW"."DAY_TRNTIME"<=:B1
Using prorated density: 9.4955e-08 of col #2 as selectivity of out-of-range/non-existent value pred --此处同样发生了谓词越界
Table: DAY_TRNFLW Alias: DAY_TRNFLW
Card: Original: 5265655.000000 Rounded: 1 Computed: 0.000010 Non Adjusted: 0.000010
.
.
.
****** finished trying bitmap/domain indexes ******
Best:: AccessPath: IndexRange -可以发现,换成date后该问题一样重现,说明和该列的数据类型无关系,而是和oracle收集直方图的机制和算法有关系
Index: INDEX_DAY_TRNFLW_C1
Cost: 4.000779 Degree: 1 Resp: 4.000779 Card: 0.000010 Bytes: 0.000000
4.9检查数据分布情况
.
.
.
03-SEP-19 40
03-SEP-19 40
03-SEP-19 40
03-SEP-19 40
02-SEP-19 40
DAY_TRNTI NUM_ROWS
--------- ----------
01-SEP-19 40
01-SEP-19 40
03-SEP-19 41
02-SEP-19 41
03-SEP-19 41
03-SEP-19 41
01-SEP-19 42
03-SEP-19 42
01-SEP-19 42
01-SEP-19 42
02-SEP-19 43
269016 rows selected.
作者介绍:
吴海存,10g/11g/12c OCM, Oracle Exadata/Golden Gate 专家, 曾于Amazon和Oracle公司担任全球业务资深DBA,目前供职于中国农业银行,担任资深数据库专家。
你公司的虚拟机还闲着?基于 Jenkins 和 Kubernetes 的持续集成测试实践了解一下! 北京四环堵车引发的智能交通大构想 400 多行代码!超详细中文聊天机器人开发指南 | 原力计划 三大运营商将上线 5G 消息;苹果谷歌联手,追踪 30 亿用户;jQuery 3.5.0 发布 | 极客头条 比特币当赎金,WannaRen 勒索病毒二度来袭! 从 Web 1.0到Web 3.0:详析这些年互联网的发展及未来方向