Oracle索引字段发生隐式转换仍然能够使用索引

Oracle索引字段发生隐式转换仍然能够使用索引

前言

最近在优化SQL过程中,发现执行计划中,某张表使用了索引但是根据执行计划谓词信息部分,发现该索引字段被隐式转换了。

在我以前的认知中,索引字段被隐式转换后是用不了索引的,因此自己构造如下实验做测试。

环境构造

14:43:47 SYS@zkm(17)> create user zkm identified by oracle;                              

User created.

Elapsed: 00:00:00.05
14:43:54 SYS@zkm(17)> grant dba to zkm;

Grant succeeded.

Elapsed: 00:00:00.01
14:43:59 SYS@zkm(17)> create table zkm.t ( n1 char(20),n2 char(20),n3 char(30));

Table created.

Elapsed: 00:00:00.10
14:44:07 SYS@zkm(17)> insert into zkm.t values('1','1','1');

1 row created.

Elapsed: 00:00:00.01
14:44:10 SYS@zkm(17)> insert into zkm.t values('2','2','2');

1 row created.

Elapsed: 00:00:00.00
14:44:14 SYS@zkm(17)> commit;

Commit complete.

Elapsed: 00:00:00.00
14:44:16 SYS@zkm(17)> create index zkm.idx on zkm.t(n1);

Index created.

Elapsed: 00:00:00.05
14:44:21 SYS@zkm(17)> analyze table zkm.t compute statistics;

Table analyzed.

Elapsed: 00:00:00.11

过程

14:48:45 SYS@zkm(17)>  conn zkm/oracle
Connected.
14:48:52 ZKM@zkm(17)> set line 500
14:48:57 ZKM@zkm(17)> set autot traceo exp
14:49:03 ZKM@zkm(17)> select /*+ index(t IDX) */ * from zkm.t where n1=1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873

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

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER("N1")=1)

14:49:11 ZKM@zkm(17)> select /*+ index(t IDX) */ * from zkm.t where n1='1';
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 2770274160

------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |     1 |    70 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T    |     1 |    70 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX  |     1 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("N1"='1')

这个时候发现并不能模拟出想要的结果。

当n1=1的时候,根据谓词信息可以得知,此时n1被做了隐式转换。

于此同时,并没法使用索引了。

研究了一会没啥头绪,于是将生产遇到的问题在群里问问看大神们。

总结下来的意思就是,隐式转换后索引失效是对于索引范围扫描、索引唯一扫描之类的,但对于索引全扫描以及快速索引全扫描是不影响的。

于是,根据下表,hint换成index_ffs再试试看。

索引 索引范围扫描 索引跳跃扫描 索引快速全扫描
index index_rs index_ss index_ffs
index_asc no_index_rs no_index_ss no_index_ffs
index_desc index_rs_asc index_ss_asc index_asc
  index_rs_desc index_ss_desc index_desc

需要注意一点的是,根据《基于Oracle的SQL调优》一书中P620处指出,

索引快速全扫描能成立的前提条件是SELECT语句中所有的查询列都存在于目标索引中,即通过扫描索引就可以得到所有的查询列而不用回表。

于是:

15:11:35 ZKM@zkm(17)> select /*+ INDEX_FFS(a IDX) */ n1 from zkm.t a where n1=1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873

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

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER("N1")=1)

15:11:36 ZKM@zkm(17)> select /*+ INDEX_FFS(a IDX) */ n1 from zkm.t a where n1='1';
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 3451708965

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |    20 |     2   (0)| 00:00:01 |
|*  1 |  INDEX FAST FULL SCAN| IDX  |     1 |    20 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("N1"='1')

还是不行,后续继续在群里抛出问题,然后中午吃饭午休了。

......
一觉醒来,丁俊大牛已经找出问题所在,要求该字段为非空。

15:21:13 ZKM@zkm(17)> alter table zkm.t modify n1 not null;

Table altered.

Elapsed: 00:00:00.16
15:21:26 ZKM@zkm(17)> select /*+ INDEX_FFS(a IDX) */ n1 from zkm.t a where n1=1;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 3451708965

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |    20 |     2   (0)| 00:00:01 |
|*  1 |  INDEX FAST FULL SCAN| IDX  |     1 |    20 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER("N1")=1)
   
15:22:25 ZKM@zkm(17)> select /*+ INDEX_FFS(a IDX) */ n1 from zkm.t a where n1='1';
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 3451708965

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |    20 |     2   (0)| 00:00:01 |
|*  1 |  INDEX FAST FULL SCAN| IDX  |     1 |    20 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("N1"='1')

果然可以了,这个其实和select count(*) from zkm.t能不能走索引的前置条件一样。

如下:

15:36:21 ZKM@zkm(17)> alter table t modify n1 null;

Table altered.

Elapsed: 00:00:00.01
15:39:41 ZKM@zkm(17)> select count(*) from zkm.t;
Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     3   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T    |     2 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------

15:39:45 ZKM@zkm(17)> select count(*) from zkm.t where n1 is not null;
Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 387325252

-------------------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT |      |     1 |    20 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE  |      |     1 |    20 |            |          |
|*  2 |   INDEX FULL SCAN| IDX  |     2 |    40 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("N1" IS NOT NULL)

不过这里Oracle比较不智能,这里n1=1已经可以排除掉null的情况了,应该是不需要特地设置n1为null或者加n1 is not null条件。

收获

  1. 具体索引的执行方式的hint,参见上表。
  2. 索引快速全扫描的限制。
  3. 隐式转换并非每次都限制索引的使用。
  4. 关于索引全扫描,网上有资料说和索引快速全扫描一样需要查询列都在目标索引中,其实不用。

第4点验证:

15:52:56 ZKM@zkm(17)> select /*+ index(t IDX) */ * from zkm.t where n1=1;
Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 2518247465

------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |     1 |    70 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T    |     1 |    70 |     2   (0)| 00:00:01 |
|*  2 |   INDEX FULL SCAN           | IDX  |     1 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(TO_NUMBER("N1")=1)
原文地址:https://www.cnblogs.com/PiscesCanon/p/13925955.html