凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1156193
  • 博文数量: 166
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3760
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-30 13:00
个人简介

about me:oracle ace pro,optimistic,passionate and harmonious. focus on oracle,mysql and other database programming,peformance tuning,db design, j2ee,linux/aix,architecture tech,etc

文章分类

全部博文(166)

文章存档

2024年(21)

2023年(28)

2022年(43)

2020年(62)

2014年(3)

2013年(9)

相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: oracle

2024-04-29 09:14:53

一般情况下like查询有三种情况:
1)%后通配可以走index range scam,
2)%前通配不能走index range scan,如果数据不能全部从索引里获取,还需要回表,则走全表扫描
3)%前通配不能走index range scan,如果数据全部从索引里获取可以走index fast full scan


数据库版本:oracle 19c
实际上本文问题至少从oracle 11g开始持续到19c一直存在。

建表语句如下:

点击(此处)折叠或打开

  1. drop table bind_test;
  2. create table bind_test as select * from dba_objects;
  3. create index idx_bind_test on bind_test(object_name);
  4. exec dbms_stats.gather_table_stats(ownname=>user,tabname=>'bind_test',no_invalidate=>false);

绑定变量peeking关闭导致like走index range scan原因及分析-凯发app官方网站

1)like后通配 非绑定变量,走index range scan,正常

点击(此处)折叠或打开

  1. select *
  2. from bind_test
  3. where object_name like 'ta%';

  4. 45 rows selected.

  5. elapsed: 00:00:00.00

  6. execution plan
  7. ----------------------------------------------------------
  8. plan hash value: 293636637

  9. -----------------------------------------------------------------------------------------------------
  10. | id | operation | name | rows | bytes | cost (%cpu)| time |
  11. -----------------------------------------------------------------------------------------------------
  12. | 0 | select statement | | 7 | 924 | 7 (0)| 00:00:01 |
  13. | 1 | table access by index rowid batched| bind_test | 7 | 924 | 7 (0)| 00:00:01 |
  14. |* 2 | index range scan | idx_bind_test | 7 | | 3 (0)| 00:00:01 |
  15. -----------------------------------------------------------------------------------------------------

  16. predicate information (identified by operation id):
  17. ---------------------------------------------------

  18.    2 - access("object_name" like 'ta%')
  19.        filter("object_name" like 'ta%')


  20. statistics
  21. ----------------------------------------------------------
  22.           0 recursive calls
  23.           0 db block gets
  24.          37 consistent gets
  25.           0 physical reads
  26.           0 redo size
  27.        8468 bytes sent via sql*net to client
  28.         436 bytes received via sql*net from client
  29.           4 sql*net roundtrips to/from client
  30.           0 sorts (memory)
  31.           0 sorts (disk)
  32.          45 rows processed
2)like前通配 非绑定变量,走全表扫描,正常

点击(此处)折叠或打开

  1. select *
  2. from bind_test
  3. where object_name like '%ta';

  4. elapsed: 00:00:00.02

  5. execution plan
  6. ----------------------------------------------------------
  7. plan hash value: 1250996780

  8. -------------------------------------------------------------------------------
  9. | id | operation | name | rows | bytes | cost (%cpu)| time |
  10. -------------------------------------------------------------------------------
  11. | 0 | select statement | | 3638 | 468k| 395 (1)| 00:00:01 |
  12. |* 1 | table access full| bind_test | 3638 | 468k| 395 (1)| 00:00:01 |
  13. -------------------------------------------------------------------------------

  14. predicate information (identified by operation id):
  15. ---------------------------------------------------

  16.    1 - filter("object_name" like '%ta' and "object_name" is not null)


  17. statistics
  18. ----------------------------------------------------------
  19.           1 recursive calls
  20.           0 db block gets
  21.        1429 consistent gets
  22.           0 physical reads
  23.           0 redo size
  24.       17156 bytes sent via sql*net to client
  25.         546 bytes received via sql*net from client
  26.          14 sql*net roundtrips to/from client
  27.           0 sorts (memory)
  28.           0 sorts (disk)
  29.         194 rows processed

3)like前通配 非绑定变量,如果不需要回表,数据全部在索引里获取,则走index fast full scan,正常

点击(此处)折叠或打开

  1. select object_name
  2. from bind_test
  3. where object_name like '%ta';

  4. elapsed: 00:00:00.02

  5. execution plan
  6. ----------------------------------------------------------
  7. plan hash value: 2253044292

  8. --------------------------------------------------------------------------------------
  9. | id | operation | name | rows | bytes | cost (%cpu)| time |
  10. --------------------------------------------------------------------------------------
  11. | 0 | select statement | | 3638 | 124k| 128 (1)| 00:00:01 |
  12. |* 1 | index fast full scan| idx_bind_test | 3638 | 124k| 128 (1)| 00:00:01 |
  13. --------------------------------------------------------------------------------------

  14. predicate information (identified by operation id):
  15. ---------------------------------------------------

  16.    1 - filter("object_name" like '%ta' and "object_name" is not null)


  17. statistics
  18. ----------------------------------------------------------
  19.           1 recursive calls
  20.           0 db block gets
  21.         485 consistent gets
  22.           8 physical reads
  23.           0 redo size
  24.        6708 bytes sent via sql*net to client
  25.         556 bytes received via sql*net from client
  26.          14 sql*net roundtrips to/from client
  27.           0 sorts (memory)
  28.           0 sorts (disk)
  29.         194 rows processed

如果绑定变量peeking关闭有bug,不管是前通配或后通配,都走index range scan
如果绑定变量值是后通配的问题不大,如果是前通配的,会导致严重性能问题
绑定变量peeking打开没有问题。
11g到19c都一样。

绑定变量peeking打开正常:
1)peeking打开,非前通配,正常走index range scan

点击(此处)折叠或打开

  1. var objname varchar2(100);
  2. exec :objname := 'ta%';
  3. set serveroutput off
  4. alter session set statistics_level=all;
  5. select *
  6. from bind_test
  7. where object_name like :objname;
  8. select * from table(dbms_xplan.display_cursor(null,null,'allstats last peeked_binds'));

  9. plan_table_output
  10. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  11. sql_id dmvrs44wk5w89, child number 0
  12. -------------------------------------
  13. select * from bind_test where object_name like :objname

  14. plan hash value: 293636637

  15. ---------------------------------------------------------------------------------------------------------------
  16. | id | operation | name | starts | e-rows | a-rows | a-time | buffers |
  17. ---------------------------------------------------------------------------------------------------------------
  18. | 0 | select statement | | 1 | | 45 |00:00:00.01 | 37 |
  19. | 1 | table access by index rowid batched| bind_test | 1 | 7 | 45 |00:00:00.01 | 37 |
  20. |* 2 | index range scan | idx_bind_test | 1 | 7 | 45 |00:00:00.01 | 6 |
  21. ---------------------------------------------------------------------------------------------------------------

  22. peeked binds (identified by position):
  23. --------------------------------------

  24.    1 - :1 (varchar2(30), csid=873): 'ta%'

  25. predicate information (identified by operation id):
  26. ---------------------------------------------------

  27.    2 - access("object_name" like :objname)
  28.        filter("object_name" like :objname)
2)peeking打开,前通配走table access full正常

点击(此处)折叠或打开

  1. select address,hash_value from v$sql where sql_id='dmvrs44wk5w89';
  2. address hash_value
  3. ---------------- ----------
  4. 00007ffe7d7138b8 958591241

  5. 先purge cursor,以防cursor共享导致演示不准:
  6. exec sys.dbms_shared_pool.purge('00007ffe7d7138b8,958591241','c');


  7. var objname varchar2(100);
  8. exec :objname := '%ta';
  9. set serveroutput off
  10. alter session set statistics_level=all;
  11. select *
  12. from bind_test
  13. where object_name like :objname;
  14. select * from table(dbms_xplan.display_cursor(null,null,'allstats last peeked_binds'));


  15. plan_table_output
  16. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  17. sql_id dmvrs44wk5w89, child number 0
  18. -------------------------------------
  19. select * from bind_test where object_name like :objname

  20. plan hash value: 1250996780

  21. -----------------------------------------------------------------------------------------
  22. | id | operation | name | starts | e-rows | a-rows | a-time | buffers |
  23. -----------------------------------------------------------------------------------------
  24. | 0 | select statement | | 1 | | 194 |00:00:00.01 | 1429 |
  25. |* 1 | table access full| bind_test | 1 | 3638 | 194 |00:00:00.01 | 1429 |
  26. -----------------------------------------------------------------------------------------

  27. peeked binds (identified by position):
  28. --------------------------------------

  29.    1 - :1 (varchar2(30), csid=873): '%ta'

  30. predicate information (identified by operation id):
  31. ---------------------------------------------------

  32.    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。


点击(此处)折叠或打开

  1. var objname varchar2(100);
  2. exec :objname := '%ta';
  3. set serveroutput off
  4. alter session set statistics_level=all;
  5. select *
  6. from bind_test
  7. where object_name like :objname;
  8. select * from table(dbms_xplan.display_cursor(null,null,'allstats last peeked_binds'));

  9. dingjun123@orclpdb> select * from table(dbms_xplan.display_cursor(null,null,'allstats last peeked_binds'));

  10. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  11. sql_id dmvrs44wk5w89, child number 1
  12. -------------------------------------
  13. select * from bind_test where object_name like :objname

  14. plan hash value: 4236497223

  15. -------------------------------------------------------------------------------------------------------
  16. | id | operation | name | starts | e-rows | a-rows | a-time | buffers |
  17. -------------------------------------------------------------------------------------------------------
  18. | 0 | select statement | | 1 | | 221 |00:00:00.05 | 542 |
  19. | 1 | table access by index rowid| bind_test | 1 | 3840 | 221 |00:00:00.05 | 542 |
  20. |* 2 | index range scan | idx_bind_test | 1 | 691 | 221 |00:00:00.05 | 396 |
  21. -------------------------------------------------------------------------------------------------------

  22. predicate information (identified by operation id):
  23. ---------------------------------------------------

  24.    2 - access("object_name" like :objname)
  25.        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,可能导致严重性能问题

点击(此处)折叠或打开

  1. var objname varchar2(100);
  2. exec :objname := 'ta';
  3. set serveroutput off
  4. alter session set statistics_level=all;
  5. select *
  6. from bind_test
  7. where object_name like '%'||:objname||'%';
  8. select * from table(dbms_xplan.display_cursor(null,null,'allstats last peeked_binds'));


  9. plan_table_output
  10. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  11. sql_id bg9sh13chmu99, child number 1
  12. -------------------------------------
  13. select * from bind_test where object_name like '%'||:objname||'%'

  14. plan hash value: 4236497223

  15. -------------------------------------------------------------------------------------------------------
  16. | id | operation | name | starts | e-rows | a-rows | a-time | buffers |
  17. -------------------------------------------------------------------------------------------------------
  18. | 0 | select statement | | 1 | | 4337 |00:00:00.07 | 3066 |
  19. | 1 | table access by index rowid| bind_test | 1 | 3840 | 4337 |00:00:00.07 | 3066 |
  20. |* 2 | index range scan | idx_bind_test | 1 | 691 | 4337 |00:00:00.06 | 671 |
  21. -------------------------------------------------------------------------------------------------------

  22. predicate information (identified by operation id):
  23. ---------------------------------------------------

  24.    2 - access("object_name" like '%'||:objname||'%')
  25.        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计算如下:

点击(此处)折叠或打开

  1. ***************************************
  2. base statistical information
  3. ***********************
  4. table stats::
  5.   table: bind_test alias: bind_test
  6.     #rows: 76807 #blks: 1120 avgrowlen: 98.00 chaincnt: 0.00
  7. index stats::
  8.   index: idx_bind_test col#: 2
  9.     lvls: 2 #lb: 379 #dk: 47304 lb/k: 1.00 db/k: 1.00 cluf: 38540.00
  10. access path analysis for bind_test
  11. ***************************************
  12. single table access path
  13.   single table cardinality estimation for bind_test[bind_test]
  14.   column (#2): object_name(
  15.     avglen: 25 ndv: 47304 nulls: 0 density: 0.000021
  16.   table: bind_test alias: bind_test
  17.     card: original: 76807.000000 rounded: 3840 computed: 3840.35 non adjusted: 3840.35
  18.   access path: tablescan
  19.     cost: 307.14 resp: 307.14 degree: 0
  20.       cost_io: 305.00 cost_cpu: 45073703
  21.       resp_io: 305.00 resp_cpu: 45073703
  22. kkofmx: index filter:"bind_test"."object_name" like '%'||:b1||'%'

  23.   access path: index (rangescan)
  24.     index: idx_bind_test
  25.     resc_io: 353.00 resc_cpu: 2949828
  26.     ix_sel: 0.009000 ix_sel_with_filters: 0.009000
  27.     cost: 304.14 resp: 304.14 degree: 1
  28.   best:: accesspath: indexrange
  29.   index: idx_bind_test
  30.          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要使用默认选择率,如下所示:

点击(此处)折叠或打开

  1. = 条件选择率:
  2. greatest(1/ndv,density)*(num_rows-null_rows)/num_rows

  3. >,<,<=,>= 条件选择率,一般情况就用0.05了:
  4. greatest(0.05,1/ndv,density)*(num_rows-null_rows)/num_rows

  5. like全表扫描选择率或回表选择率:常数是0.05
  6. greatest(0.05,1/ndv,density)*(num_rows-null_rows)/num_rows

  7. like条件index range scan选择率:常数不是0.05,是0.009
  8. greatest(0.009,1/ndv,density)*(num_rows-null_rows)/num_rows
为什么公式里还有density,因为如果带直方图的话,density就不是1/ndv,没有直方图density=1/ndv:

为了验证like带绑定变量,无peeking的选择率计算公式,可以找个density比0.009大的验证下:

点击(此处)折叠或打开

  1. select column_name,num_distinct,density,num_nulls from dba_tab_col_statistics where table_name='bind_test';

  2. column_name num_distinct density num_nulls
  3. ------------------------------ ------------ ---------- ----------
  4. edition_name 0 0 76807
  5. namespace 21 .047619048 0
  6. secondary 2 .5 0
  7. generated 2 .5 0
  8. temporary 2 .5 0
  9. status 2 .5 0
  10. timestamp 1663 .000601323 0
  11. last_ddl_time 1583 .000631712 0
  12. created 1529 .000654022 0
  13. object_type 45 .022222222 0
  14. data_object_id 10551 .000094778 66206
  15. object_id 76807 .00001302 0
  16. subobject_name 271 .003690037 76154
  17. object_name 47304 .00002114 0
  18. owner 36 .027777778 0
可以用owner列,density=.027777778 > 0.009,为了验证,建个索引:

点击(此处)折叠或打开

  1. create index idx_bind_test_owner on bind_test(owner);

  2. var owner varchar2(100);
  3. select * from bind_test
  4. where owner like :owner;

  5. execution plan
  6. ----------------------------------------------------------
  7. plan hash value: 585688886

  8. ---------------------------------------------------------------------------------------------------
  9. | id | operation | name | rows | bytes | cost (%cpu)| time |
  10. ---------------------------------------------------------------------------------------------------
  11. | 0 | select statement | | 3840 | 367k| 65 (0)| 00:00:01 |
  12. | 1 | table access by index rowid| bind_test | 3840 | 367k| 65 (0)| 00:00:01 |
  13. |* 2 | index range scan | idx_bind_test_owner | 2135 | | 7 (0)| 00:00:01 |
  14. ---------------------------------------------------------------------------------------------------

  15. predicate information (identified by operation id):
  16. ---------------------------------------------------

  17.    2 - access("owner" like :owner)
  18.        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绑定解决(建议使用)。




阅读(42) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图