Oracle 颠覆认知的无函数处理限定条件字段也可以用上函数索引

 

Oracle 颠覆认知的无函数处理限定条件字段也可以用上函数索引

 

前言

之前搞SQL优化过程中,遇到一个限定条件为where create_time >= sysdate - 7之后用上函数索引的情况,这个索引创建之初使用了trunc(create_time)做了处理。

这个就有点颠覆我之前的认知了--函数索引一定只能被同样函数处理过的字段才能使用(前提是需要回表),结果其实不是100%一定的。

现在记录一下这个情况。

 

现象

对SQL内容涉及保密因此会做适当处理,不影响内容阅读理解。

SQL文本如下:

SELECT T.CREATE_TIME,
       L.IDEN_SEQ_NO,
       L.ABCD_TOTAL,
       L.OPERA_CURR,
       L.AAA_TOTAL,
       L.BBB_TOTAL,
  FROM ID_PRE_HEAD T
 INNER JOIN ID_PRE_LIST L
    ON T.IDEN_SEQ_NO = L.IDEN_SEQ_NO
 WHERE L.BBB_TOTAL IS NULL
   AND L.AAA_TOTAL IS NULL
   AND T.CREATE_TIME IS NOT NULL
   AND T.CREATE_TIME >= SYSDATE - 7
   AND LENGTH(L.OPERA_CURR) = 3
   AND T.IS_REAL = '0'
   AND ROWNUM <= 5000;

 

通过设置statistics_level=all后得到运行完成后的执行计划为:

Plan hash value: 2382519203

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                        |      1 |        |      1 |00:00:02.58 |     643K|
|*  1 |  COUNT STOPKEY                 |                        |      1 |        |      1 |00:00:02.58 |     643K|
|   2 |   NESTED LOOPS                 |                        |      1 |      1 |      1 |00:00:02.58 |     643K|
|   3 |    NESTED LOOPS                |                        |      1 |      5 |   1126K|00:00:01.48 |     551K|
|*  4 |     TABLE ACCESS BY INDEX ROWID| ID_PRE_HEAD            |      1 |      1 |    216K|00:00:00.49 |   57729 |
|*  5 |      INDEX RANGE SCAN          | IDX_F_CREATE_TIME      |      1 |  26639 |    235K|00:00:00.05 |    1659 |
|*  6 |     INDEX RANGE SCAN           | IDX_ID_PRE_LIST_01     |    216K|      5 |   1126K|00:00:00.81 |     494K|
|*  7 |    TABLE ACCESS BY INDEX ROWID | ID_PRE_LIST            |   1126K|      1 |      1 |00:00:00.87 |   91481 |
-------------------------------------------------------------------------------------------------------------------

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

   1 - filter(ROWNUM<=5000)
   4 - filter(("T"."IS_REAL"='0' AND "T"."CREATE_TIME">=SYSDATE@!-7))
   5 - access("T"."SYS_NC00136$">=TRUNC(SYSDATE@!-7))
   6 - access("T"."IDEN_SEQ_NO"="L"."IDEN_SEQ_NO")
   7 - filter(("L"."BBB_TOTAL" IS NULL AND "L"."AAA_TOTAL" IS NULL AND LENGTH("L"."OPERA_CURR")=3))

 

其中,函数索引IDX_F_CREATE_TIME的创建语句为:

create index IDX_F_CREATE_TIME on ID_PRE_HEAD(trunc(create_time)) online;

 

 

由此可见,条件

T.CREATE_TIME >= SYSDATE - 7

用上了函数索引

IDX_F_CREATE_TIME

从执行计划的ID=5步骤和对应的谓词信息(5 - access("T"."SYS_NC00136$">=TRUNC(SYSDATE@!-7)))可以看出,先是通过函数索引拿出TRUNC(SYSDATE@!-7)的数据,然后在这部分的数据中再过滤T.CREATE_TIME >= SYSDATE - 7的数据。

这么看的话,CBO还是非常聪明的,巧妙的使用上了索引避免了全表扫描。

 

猜想

基于以上事实,由于函数索引被trunc做了处理,而实际上trunc(sysdate-7)确实包含了sysdate -7的数据,因此CBO才可以利用上索引。

因此,“无函数处理限定条件字段可以用上函数索引”这原因应该就是即便用上该索引也不会导致最后SQL结果数据有问题。

 

可以试试反过来的情况,对限定条件做函数处理,但是限定条件的字段的索引是普通索引。

生产刚好有这样环境,如下(做脱敏处理):

select /*+  index(t IDX_CREATE_TIME_00) */ distinct IS_CHECK from ID_PRE_LIST t where trunc(CREATE_TIME) >=sysdate - 1/2400;

对应执行计划:
Plan hash value: 1103666447 ------------------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem | ------------------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 0 |00:02:25.52 | 206K| 114K| | | | | 1 | HASH UNIQUE | | 1 | 2 | 0 |00:02:25.52 | 206K| 114K| 1394K| 1394K| | | 2 | TABLE ACCESS BY INDEX ROWID| ID_PRE_LIST | 1 | 2173K| 0 |00:02:25.52 | 206K| 114K| | | | |* 3 | INDEX FULL SCAN | IDX_CREATE_TIME_00 | 1 | 2173K| 0 |00:02:25.52 | 206K| 114K| | | | ------------------------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter(TRUNC(INTERNAL_FUNCTION("CREATE_TIME"))>=SYSDATE@!-.000416666666666666666666666666666666666667)

从结果看,果然即便是trunc(create_Time),也可以用上普通索引IDX_CREATE_TIME_00 。

(不要纠结用hint强制走索引,能够强制成功说明有能够使用的前提,不使用hint不走索引仅仅因为cost判断问题而已)

 

那么,可以猜想什么时候会出现这类情况,就是能够通过索引获得的数据范围要比原来的大。

比如第一种情况用了函数索引过滤出TRUNC(SYSDATE@!-7)的数据,肯定要包含SQL中的T.CREATE_TIME >= SYSDATE - 7的数据,

比如第二种情况,ID=3以及对应的谓词信息可以知道,访问索引IDX_CREATE_TIME_00的时候是全索引扫描而不是索引范围扫,并且谓词信息出是filter而不是access。

 

当然,显然并不适用于大部分的情况,类似函数索引:create index idx_name on xxx(length(name));这类会完全丢失原本字段内容的情况(length之后变为数字长度的值了)那是肯定无法出现前边的两种情况的。

至此。

 

posted @ 2021-02-07 14:16  PiscesCanon  阅读(175)  评论(1编辑  收藏  举报