-
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绑定解决(建议使用)。