-
select address,hash_value from v$sql where sql_id='dmvrs44wk5w89';
-
ADDRESS HASH_VALUE
-
---------------- ----------
-
00007FFE7D7138B8 958591241
-
-
先purge cursor,以防cursor共享导致演示不准:
-
exec sys.dbms_shared_pool.purge('00007FFE7D7138B8,958591241','C');
-
-
-
var objname varchar2(100);
-
exec :objname := '%TA';
-
set serveroutput off
-
alter session set statistics_level=all;
-
select *
-
from bind_test
-
where object_name like :objname;
-
select * from table(dbms_xplan.display_cursor(null,null,'allstats last +peeked_binds'));
-
-
-
PLAN_TABLE_OUTPUT
-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
SQL_ID dmvrs44wk5w89, child number 0
-
-------------------------------------
-
select * from bind_test where object_name like :objname
-
-
Plan hash value: 1250996780
-
-
-----------------------------------------------------------------------------------------
-
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-
-----------------------------------------------------------------------------------------
-
| 0 | SELECT STATEMENT | | 1 | | 194 |00:00:00.01 | 1429 |
-
|* 1 | TABLE ACCESS FULL| BIND_TEST | 1 | 3638 | 194 |00:00:00.01 | 1429 |
-
-----------------------------------------------------------------------------------------
-
-
Peeked Binds (identified by position):
-
--------------------------------------
-
-
1 - :1 (VARCHAR2(30), CSID=873): '%TA'
-
-
Predicate Information (identified by operation id):
-
---------------------------------------------------
-
-
1 - filter("OBJECT_NAME" LIKE :OBJNAME)
3.绑定变量peeking关闭,like :bind 或like '%'||:bind形式的都走INDEX RANGE SCAN
前面的好理解,因为peeking关闭,like :bind CBO可能把绑定变量当成不带%的计算选择率,这样一般选择率density,density一般很小,走索引
(事实上不是这样,是和like '%'||:bind选择率计算方式一样)。
后面的是like '%'||:bind,这种显式写了前通配,因为peeking关闭导致走INDEX RANGE SCAN,可以认为是BUG(这个BUG貌似一直存在),因为
这种走索引,肯定是从索引{BANNED}最佳左侧扫描全部索引树,除非有rownum<...可能只需要前面少量leaf block。
关闭peeking来演示:
alter session set "_optim_peek_user_binds"=false;
1)绑定变量peeking关闭,传入的值是前通配,也走INDEX RANGE SCAN。
-
var objname varchar2(100);
-
exec :objname := '%TA';
-
set serveroutput off
-
alter session set statistics_level=all;
-
select *
-
from bind_test
-
where object_name like :objname;
-
select * from table(dbms_xplan.display_cursor(null,null,'allstats last +peeked_binds'));
-
-
dingjun123@ORCLPDB> select * from table(dbms_xplan.display_cursor(null,null,'allstats last +peeked_binds'));
-
-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
SQL_ID dmvrs44wk5w89, child number 1
-
-------------------------------------
-
select * from bind_test where object_name like :objname
-
-
Plan hash value: 4236497223
-
-
-------------------------------------------------------------------------------------------------------
-
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-
-------------------------------------------------------------------------------------------------------
-
| 0 | SELECT STATEMENT | | 1 | | 221 |00:00:00.05 | 542 |
-
| 1 | TABLE ACCESS BY INDEX ROWID| BIND_TEST | 1 | 3840 | 221 |00:00:00.05 | 542 |
-
|* 2 | INDEX RANGE SCAN | IDX_BIND_TEST | 1 | 691 | 221 |00:00:00.05 | 396 |
-
-------------------------------------------------------------------------------------------------------
-
-
Predicate Information (identified by operation id):
-
---------------------------------------------------
-
-
2 - access("OBJECT_NAME" LIKE :OBJNAME)
-
filter("OBJECT_NAME" LIKE :OBJNAME)
这种E-Row计算方式和2)like '%'||:bind一样,见下面的分析,与猜想的按照类似等值查询不一样,如果按照等值查询返回行是:
num_rows*density=76807*.00002114=2行。
2)绑定变量peeking关闭,显式编码前通配,使用like '%'||:bind形式,这种按理是和peeking无关的,走不了INDEX RANGE SCAN,
但是执行计划缺走了INDEX RANGE SCAN,这是BUG,可能导致严重性能问题。
-
var objname varchar2(100);
-
exec :objname := 'TA';
-
set serveroutput off
-
alter session set statistics_level=all;
-
select *
-
from bind_test
-
where object_name like '%'||:objname||'%';
-
select * from table(dbms_xplan.display_cursor(null,null,'allstats last +peeked_binds'));
-
-
-
PLAN_TABLE_OUTPUT
-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
SQL_ID bg9sh13chmu99, child number 1
-
-------------------------------------
-
select * from bind_test where object_name like '%'||:objname||'%'
-
-
Plan hash value: 4236497223
-
-
-------------------------------------------------------------------------------------------------------
-
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-
-------------------------------------------------------------------------------------------------------
-
| 0 | SELECT STATEMENT | | 1 | | 4337 |00:00:00.07 | 3066 |
-
| 1 | TABLE ACCESS BY INDEX ROWID| BIND_TEST | 1 | 3840 | 4337 |00:00:00.07 | 3066 |
-
|* 2 | INDEX RANGE SCAN | IDX_BIND_TEST | 1 | 691 | 4337 |00:00:00.06 | 671 |
-
-------------------------------------------------------------------------------------------------------
-
-
Predicate Information (identified by operation id):
-
---------------------------------------------------
-
-
2 - access("OBJECT_NAME" LIKE '%'||:OBJNAME||'%')
-
filter("OBJECT_NAME" LIKE '%'||:OBJNAME||'%')
上面的like前导%的走INDEX RANGE SCAN,而且ID=2返回行是691比ID=1的3840少很多,这很让人迷惑,ID=1没有条件,按理说ID=1应该
只会比ID=2少,唯一可能的情况是ID=2的选择率计算方式和ID=1的不一样,还是通过10053看:
10053 Access path analysis计算如下:
-
***************************************
-
BASE STATISTICAL INFORMATION
-
***********************
-
Table Stats::
-
Table: BIND_TEST Alias: BIND_TEST
-
#Rows: 76807 #Blks: 1120 AvgRowLen: 98.00 ChainCnt: 0.00
-
Index Stats::
-
Index: IDX_BIND_TEST Col#: 2
-
LVLS: 2 #LB: 379 #DK: 47304 LB/K: 1.00 DB/K: 1.00 CLUF: 38540.00
-
Access path analysis for BIND_TEST
-
***************************************
-
SINGLE TABLE ACCESS PATH
-
Single Table Cardinality Estimation for BIND_TEST[BIND_TEST]
-
Column (#2): OBJECT_NAME(
-
AvgLen: 25 NDV: 47304 Nulls: 0 Density: 0.000021
-
Table: BIND_TEST Alias: BIND_TEST
-
Card: Original: 76807.000000 Rounded: 3840 Computed: 3840.35 Non Adjusted: 3840.35
-
Access Path: TableScan
-
Cost: 307.14 Resp: 307.14 Degree: 0
-
Cost_io: 305.00 Cost_cpu: 45073703
-
Resp_io: 305.00 Resp_cpu: 45073703
-
kkofmx: index filter:"BIND_TEST"."OBJECT_NAME" LIKE '%'||:B1||'%'
-
-
Access Path: index (RangeScan)
-
Index: IDX_BIND_TEST
-
resc_io: 353.00 resc_cpu: 2949828
-
ix_sel: 0.009000 ix_sel_with_filters: 0.009000
-
Cost: 304.14 Resp: 304.14 Degree: 1
-
Best:: AccessPath: IndexRange
-
Index: IDX_BIND_TEST
-
Cost: 304.14 Degree: 1 Resp: 304.14 Card: 3840.35 Bytes: 0
结论:为什么绑定变量peeking会出现不管%是显式写还是写在绑定变量的值里,都可能走INDEX RANGE SCAN呢?
从10053看,CBO没有考虑显式前导%走不了索引的问题,当然%写在绑定变量值里也不会考虑,
主要是索引访问选择率ix_sel是0.009,相当于0.9%,因为CBO没有考虑%,这可能导致走索引。
这个0.009貌似是个默认值(无peeking的like默认sel):
在10053里回表过滤的选择率也是0.009,但是执行计划显示的回表选择率还是按照5%(实际回表计算的sel和10053显示不一样)
也就是ix_sel实际上是按照0.009,ix_sel_with_filters实际上按照0.05,导致ID=2的返回行计算是691行,然后ID=1回表没有任何过滤条件,
因为回表按照5%计算,导致行数比ID=2的多很多,这是优化器算法层面的BUG。
具体计算过程如下:
ID=2 INDEX RANGE SCAN E-Rows = round(76807*.009) = 691行
ID=1 TABLE ACCESS BY INDEX ROWID E-Rows = round(76807*.05) = 3840行
实际计算ix_sel:0.009,ix_sel_with_filters:0.05
与10053显示的回表过滤选择率ix_sel_with_filters不一样,10053的如下:
ix_sel: 0.009000 ix_sel_with_filters: 0.009000
一般情况下,绑定变量+无peeking要使用默认选择率,如下所示:
-
= 条件选择率:
-
greatest(1/NDV,DENSITY)*(num_rows-null_rows)/num_rows
-
-
>,<,<=,>= 条件选择率,一般情况就用0.05了:
-
greatest(0.05,1/NDV,DENSITY)*(num_rows-null_rows)/num_rows
-
-
like全表扫描选择率或回表选择率:常数是0.05
-
greatest(0.05,1/NDV,DENSITY)*(num_rows-null_rows)/num_rows
-
-
like条件index range scan选择率:常数不是0.05,是0.009
-
greatest(0.009,1/NDV,DENSITY)*(num_rows-null_rows)/num_rows
为什么公式里还有density,因为如果带直方图的话,density就不是1/NDV,没有直方图density=1/NDV:
为了验证like带绑定变量,无peeking的选择率计算公式,可以找个density比0.009大的验证下:
-
select column_name,num_distinct,density,num_nulls from dba_tab_col_statistics where table_name='BIND_TEST';
-
-
COLUMN_NAME NUM_DISTINCT DENSITY NUM_NULLS
-
------------------------------ ------------ ---------- ----------
-
EDITION_NAME 0 0 76807
-
NAMESPACE 21 .047619048 0
-
SECONDARY 2 .5 0
-
GENERATED 2 .5 0
-
TEMPORARY 2 .5 0
-
STATUS 2 .5 0
-
TIMESTAMP 1663 .000601323 0
-
LAST_DDL_TIME 1583 .000631712 0
-
CREATED 1529 .000654022 0
-
OBJECT_TYPE 45 .022222222 0
-
DATA_OBJECT_ID 10551 .000094778 66206
-
OBJECT_ID 76807 .00001302 0
-
SUBOBJECT_NAME 271 .003690037 76154
-
OBJECT_NAME 47304 .00002114 0
-
OWNER 36 .027777778 0
可以用owner列,density=.027777778 > 0.009,为了验证,建个索引:
-
create index idx_bind_test_owner on bind_test(owner);
-
-
var owner varchar2(100);
-
select * from bind_test
-
where owner like :owner;
-
-
Execution Plan
-
----------------------------------------------------------
-
Plan hash value: 585688886
-
-
---------------------------------------------------------------------------------------------------
-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-
---------------------------------------------------------------------------------------------------
-
| 0 | SELECT STATEMENT | | 3840 | 367K| 65 (0)| 00:00:01 |
-
| 1 | TABLE ACCESS BY INDEX ROWID| BIND_TEST | 3840 | 367K| 65 (0)| 00:00:01 |
-
|* 2 | INDEX RANGE SCAN | IDX_BIND_TEST_OWNER | 2135 | | 7 (0)| 00:00:01 |
-
---------------------------------------------------------------------------------------------------
-
-
Predicate Information (identified by operation id):
-
---------------------------------------------------
-
-
2 - access("OWNER" LIKE :OWNER)
-
filter("OWNER" LIKE :OWNER)
ID=2的选择率按照公式:greatest(0.009,1/NDV,DENSITY)*(num_rows-null_rows)/num_rows= .027777778
计算的行数
select 76807 * .027777778 from dual;
2134 接近2135
然后ID=1还是按照0.05计算=3840行。
另外从12c开始还有几个like :bind写法的BUG也是走index range scan,上述例子在11g里同样存在:
见BUG:Index SCAN Instead of FAST FULL SCAN With COL LIKE '%bind Variable%' (Doc ID 2781949.1)
综上所述,建议如下:
1)影响优化器的默认参数,除非出现重大影响或确定影响生产的BUG的需要关闭,一般都用默认值,特别是
以_optimizer开头的隐含参数,比如:_optimizer_cost_based_transformation、_optimizer_squ_bottomup
2)绑定变量peeking{BANNED}最佳好不要关闭,关闭了影响直方图使用,无法计算真实的数据分布,
如果查询总是查询某个分布很少的值,有了peeking会走索引,没有peeking就按照默认选择率,可能不走索引。
比如status=1,0,其中1很多,0很少,基本按照status='0'查询,这样走索引好,如果
关闭peeking,按照where status = :var查询,选择率是max(1/2,density)则走不了索引.
关闭peeking,可能会导致like :bind或like '%'||:bind形式的条件走INDEX RANGE SCAN,见本篇文章。
如果绑定变量peeking遇到直方图,传入的值对应分布区间变动频繁,不同分布要求不一样执行计划,
ORACLE有adaptive cursor sharding(BUG多,一般关闭)或可以使用BIND_AWARE HINTS+SQL DIRECTIVE/SQL PROFILE绑定解决(建议使用)。