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条件。
收获
- 具体索引的执行方式的hint,参见上表。
- 索引快速全扫描的限制。
- 隐式转换并非每次都限制索引的使用。
- 关于索引全扫描,网上有资料说和索引快速全扫描一样需要查询列都在目标索引中,其实不用。
第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)