zoukankan      html  css  js  c++  java
  • 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之后变为数字长度的值了)那是肯定无法出现前边的两种情况的。

    至此。

  • 相关阅读:
    基本内置类型
    多维数组
    数组
    迭代器
    标准库类型 vector
    标准库类型 string
    运算符优先级表
    类型转换
    sizeof 和逗号运算符
    位运算符
  • 原文地址:https://www.cnblogs.com/PiscesCanon/p/14384380.html
Copyright © 2011-2022 走看看