ITPub博客

首页 > 数据库 > Oracle > SQL优化经典----为什么不走索引

SQL优化经典----为什么不走索引

Oracle 作者:请叫我黑哥 时间:2013-12-08 23:16:46 0 删除 编辑

     在SQL调优时,我们常常会遇到表所在的字段明明有索引,但是ORACLE优化器并没有选择走索引而是走的全表扫描,就算我手工给它添加hint 也济于事,今天做实验发现有两种情况下是不走索引的,实验过程如下:

实验一:字段类型设计或SQL条件写法有误导致无法选择索引

drop table t_col_type purge;
create table t_col_type(id varchar2(20),col2 varchar2(20),col3 varchar2(20));
insert into t_col_type select rownum,'abc','efg' from dual connect by level<=10000;
commit;
create index idx_id on t_col_type(id);
set linesize 1000
set autotrace traceonly
select * from t_col_type where id=6;

--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |     1 |    36 |     9   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T_COL_TYPE |     1 |    36 |     9   (0)| 00:00:01 |
--------------------------------------------------------------------------------

奇怪吧?明明id列有索引为什么还走全表扫描?统计信息不准造成的?那我们收集一下统计信息再执行

exec dbms_stats.gather_table_stats(user,'t_col_type',cascade=>true);

select * from t_col_type where id=6;

--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |     1 |    36 |     9   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T_COL_TYPE |     1 |    36 |     9   (0)| 00:00:01 |
--------------------------------------------------------------------------------

1 - filter(TO_NUMBER("ID")=6)

执行计划没变,后来发现红色加粗部分对条件进行了类型转换,因此ORACLE无法选择索引,修改where条件id='6'看看效果如何:

select * from t_col_type where id='6';
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     1 |    36 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T_COL_TYPE |     1 |    36 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_ID     |     1 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------
2 - access("ID"='6')

从执行计划来看,查询语句选择走索引,并且执行计划2中变量类型没有转换

实验结论:在SQL写法或在表设计时,选择正确的类型很关键,如果表是varchar2类型,那么where条件中该列值就要添加引号,否则会造成ORACLE生成错误执行计划

知识补充:

select * from t_col_type where to_number(id)=6

select * from t_col_type where to_number = 6

就该实验表的字段类型来看,上面这两条SQL是完全等价的,虽然SQL_ID不一样,但执行计划完全一样,但是

select * from t_col_type where to_number(id)=6

该SQL是开发中新手容易犯错的地方,对where条件后面的谓语加函数进行处理也是SQL执行低下案例中常见的类型,正确的写法应该是将函数处理加在=号后面

select * from t_col_type where id=to_char(6)

这个案例中有朋友可能会说我添加hint 会不会走索引呢?答案是:不会,有兴趣的朋友自己做实验

其实知识补充这个案例完全可以当做一个执行计划不走索引经典案例,不说了,看实验二

 

实验二:列中有null值,导致ORACLE无法选择索引

create table test as select * from dba_objects;
create index i_object_id on test(object_id);
exec dbms_stats.gather_table_stats(user, 'TEST',cascade => true);
set autot trace exp stat
select max(object_id),min(object_id) from test;
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     5 |   283   (1)| 00:00:04 |
|   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |
|   2 |   TABLE ACCESS FULL| TEST | 72572 |   354K|   283   (1)| 00:00:04 |
---------------------------------------------------------------------------
是不是也很奇怪?明明有索引为什么不走索引却走全表扫描呢?这个案例可以完全排除实验一中的可能,此类中我添加一个hint 效果如何呢?

select max(object_id),min(object_id) from test;

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     5 |   283   (1)| 00:00:04 |
|   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |
|   2 |   TABLE ACCESS FULL| TEST | 72572 |   354K|   283   (1)| 00:00:04 |
---------------------------------------------------------------------------

从执行计划输出来看,添加hint在这里也是无用武之地,那我添加一个where条件如何?

select max(object_id),min(object_id) from test where object_id>0;

-------------------------------------------------------------------------------------
| Id  | Operation             | Name        | Rows  | Bytes | Cost (%CPU)| Time    |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |     1 |     5 |    46   (3)| 00:00:01 |
|   1 |  SORT AGGREGATE       |             |     1 |     5 |            |    |
|*  2 |   INDEX FAST FULL SCAN| I_OBJECT_ID | 72572 |   354K|    46   (3)| 00:00:01 |
-------------------------------------------------------------------------------------
奇怪现像再次发生,ORACLE这次选择了走索引,难道object_id值还有其它类型?原来需要对该列添加not null约束才行,因为test表中字段objet_id并没有指明是not null,ORACLE优化器创建索引时认为可能存在null类型的值,故走了全表扫描。删除null列,添加not null约束,执行计划走了索引

delete from test where object_id is null;   --我的是11.2.0.1.0
alter table test modify object_id not null;

select max(object_id),min(object_id) from test where object_id>0;

-------------------------------------------------------------------------------------
| Id  | Operation             | Name        | Rows  | Bytes | Cost (%CPU)| Time    |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |     1 |     5 |    46   (3)| 00:00:01 |
|   1 |  SORT AGGREGATE       |             |     1 |     5 |            |    |
|*  2 |   INDEX FAST FULL SCAN| I_OBJECT_ID | 72572 |   354K|    46   (3)| 00:00:01 |
-------------------------------------------------------------------------------------

实验结论:对于索引的创建,一定要创建在not null列上,如果创建的索引列是nullable,那么该索引实际上是无效的,切记!

     对于执行计划不走索引还有可能是分区表中分区不平衡、绑定变更窥视等原因造成,具体案例详见:

     http://www.hellodba.com/reader.php?ID=55&lang=CN

实验三:创建反向索引导致执行计划不走索引

drop table t purge;
create table t (id number,deal_date date,area_code number,nbr number,contents varchar2(4000));
set autotrace off
insert into t(id,deal_date,area_code,nbr,contents)
      select rownum,
             to_date( to_char(sysdate-365,'J')+TRUNC(DBMS_RANDOM.VALUE(0,700)),'J'),
             ceil(dbms_random.value(590,599)),
             ceil(dbms_random.value(18900000001,18999999999)),
             rpad('*',400,'*')
        from dual
      connect by rownum <= 100000;
commit;

--创建正常索引

create index idx_normal on t(id);

select * from t where id>=28 and id<=50;
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)|Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |    23 | 47150 |     4   (0)|00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T          |    23 | 47150 |     4   (0)|00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_NORMAL |    23 |       |     2   (0)|00:00:01 |
------------------------------------------------------------------------------------------

--创建反向索引

create index idx_reverse on t(id) reverse;
select * from t where id>=28 and id<=50;
执行计划
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   304 |   608K|  1709   (1)| 00:00:21 |
|*  1 |  TABLE ACCESS FULL| T    |   304 |   608K|  1709   (1)| 00:00:21 |
--------------------------------------------------------------------------

实验结论:反向索引可以很好解决索引热块问题,但是要明确一点,反向索引只适用于等值查询,如果业务中存大大量范围查询,那么创建反向索引会导致执行计划走全表扫描,创建反向索引需要仔细确认业务需要(切记)

温馨提示:使用hint指定index的时候需要慎重,如果不合适或者无效,可能导致程序效率更低,因为如果我们使用hint指定了一个无效的index,优化器会扫描表中所有可以使用的index,然后选择cost最小的index或者index组合,而不会选择全表扫描。

    SQL优化时,我们可以通过创建虚拟索引查看创建索引以后,执行计划信息,

ORACLE虚拟索引说解:http://blog.sina.com.cn/s/blog_61cd89f60102eeyb.html

<!-- 正文结束 -->

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/29257188/viewspace-1116222/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2013-10-17