Oracle不使用具有动态time_interval值的索引,导致成本增加

yv5phkfx  于 2023-10-16  发布在  Oracle
关注(0)|答案(2)|浏览(89)

我有一个与Oracle查询相关的性能问题,其中成本会根据我如何设置time_interval值而发生显著变化。
当我硬编码time_interval值时(例如,WHERE o.request_time > SYSDATE - 15/1440),执行计划显示它使用了索引,并且成本很低。
然而,当我使用来自联接表的动态值时(例如,WHERE o.request_time > SYSDATE - c.time_interval/1440),执行计划不使用索引,成本会显著增加。
下面是我的查询的简化版本:

SELECT ...
FROM table_1 o
    ,table_2 c 
WHERE o.request_time > SYSDATE - c.time_interval/1440;

在表1中,请求时间被索引。
我期望执行计划一致地使用索引,而不管time_interval是静态的还是从联接表中动态获取的。我的主要目标是理解为什么动态time_interval会阻止优化器使用索引,并优化查询以确保持续的低成本。
这是实际查询:

SELECT c.state
      ,c.critical_msg_quotes
      ,COUNT(1) AS critical_msg_count
FROM  operations    o
     ,configuration c
WHERE o.state = c.state
  AND o.request_time > sysdate - c.time_interval / 1440
GROUP BY c.state
        ,c.critical_msg_quotes
cwtwac6a

cwtwac6a1#

当您使用像SYSDATE - 15/1440这样的文字时,Oracle可以查询列high_valuelow_valuenum_distinct和统计数据收集(DBA_TAB_COL_STATISTICS)收集的直方图,以计算您的行中可能有多少百分比的日期在这个狭窄的范围内。使用这些信息,它可以确定您需要相对于整个表的非常少量的行,这使得使用索引成为一个更有吸引力的选择。
但是,当您使用来自其他地方的值时,Oracle无法预测该值将是什么,因为它取决于两个表之间的连接结果。在这种情况下,优化器对基数做了更宽松的假设,并且它的计算经常会有很大的偏差(数量级上)。如果它相信你的 predicate 将匹配表的重要部分,它将绕过索引并进行全表扫描,如果它的假设是正确的,这将是最好的方法。当您需要的不仅仅是表的一个非常小的切片时,完全扫描比使用索引更好。甲骨文只是试图根据它所知道的有限的东西来选择最好的。
你知道一些它不知道的事:该连接的结果将给予time_interval值,该值将驱动非常小的时间窗口。要解决这个问题,您应该在(state,request_time)上按该顺序建立一个两列索引,并向查询添加一个提示:

CREATE INDEX operations_1ix ON operations (state,request_time) COMPRESS 1;

SELECT /*+ LEADING(c) USE_NL_WITH_INDEX(o) */
       c.state
      ,c.critical_msg_quotes
      ,COUNT(1) AS critical_msg_count
FROM  operations    o
     ,configuration c
WHERE o.state = c.state
  AND o.request_time > sysdate - c.time_interval / 1440
GROUP BY c.state
        ,c.critical_msg_quotes

最后,请忽略COST,它在性能调优中没有任何作用。如果Oracle犯了一个错误,根据定义,它对COST的计算是不正确的,所以这个值应该没有任何意义。Oracle将 * 始终 * 选择具有最低COST值的计划。但这是一个很难得到正确的数字。

mu0hgdu0

mu0hgdu02#

request_time上的索引不适合您的查询,您需要state,request_time上的索引
如果你有一个最新版本的Oracle(>= 12),你应该看到一个 * 自适应计划 *,它可以在哈希连接和对操作表上的索引范围扫描的嵌套循环的完全访问之间切换-参见下面的示例

--------------------------------------------------------------
|   Id  | Operation                | Name           | Starts |
--------------------------------------------------------------
|     0 | SELECT STATEMENT         |                |      1 |  
|     1 |  HASH GROUP BY           |                |      1 |   
|  *  2 |   HASH JOIN              |                |      1 |     
|-    3 |    NESTED LOOPS          |                |      1 |     
|-    4 |     STATISTICS COLLECTOR |                |      1 |     
|     5 |      TABLE ACCESS FULL   | CONFIGURATION  |      1 |    
|- *  6 |     INDEX RANGE SCAN     | OPERATIONS_IDX |      0 |  
|     7 |    INDEX FULL SCAN       | OPERATIONS_IDX |      1 |    
--------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("O"."STATE"="C"."STATE")
       filter("O"."REQUEST_TIME">SYSDATE@!-"C"."TIME_INTERVAL"/1440)
   6 - access("O"."STATE"="C"."STATE" AND "O"."REQUEST_TIME">SYSDATE@!-"C"."TIME_INTERVAL"/1440 AND "O"."REQUEST_TIME" IS NOT NULL)
 
Note
-----
   - this is an adaptive plan (rows marked '-' are inactive)

如果你的查询仍然有性能问题,下一个选择是对操作表进行 * 范围分区 *(如果是这样,问一个新的问题)。

相关问题