【问题标题】:Index is not being used by optimizer优化器未使用索引
【发布时间】:2016-05-01 21:37:07
【问题描述】:

我有一个查询由于对表进行全面扫描而执行非常糟糕。我检查了重建索引的统计信息,但它不起作用。

SQL 语句:

select distinct NA_DIR_EMAIL d, NA_DIR_EMAIL r
from gcr_items , gcr_deals
where gcr_deals.GCR_DEALS_ID=gcr_items.GCR_DEALS_ID 
and 
gcr_deals.bu_id=:P0_BU_ID
and 
decode(:P55_DIRECT,'ALL','Y',trim(upper(NA_ORG_OWNER_EMAIL)))=
decode(:P55_DIRECT,'ALL','Y',trim(upper(:P55_DIRECT))) 
order by 1

Execution Plan :
Plan hash value: 3180018891

-------------------------------------------------------------------------
| Id  | Operation                 | Name             | Rows  | Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                  |     8 | 00:11:42 |
|   1 |  SORT ORDER BY            |                  |     8 | 00:11:42 |
|   2 |   HASH UNIQUE             |                  |     8 | 00:11:42 |
|*  3 |    HASH JOIN              |                  |  7385 | 00:11:42 |
|*  4 |     VIEW                  | index$_join$_002 | 10462 | 00:00:05 |
|*  5 |      HASH JOIN            |                  |       |          |
|*  6 |       INDEX RANGE SCAN    | GCR_DEALS_IDX12  | 10462 | 00:00:01 |
|   7 |       INDEX FAST FULL SCAN| GCR_DEALS_IDX1   | 10462 | 00:00:06 |
|*  8 |     TABLE ACCESS FULL     | GCR_ITEMS        |  7386 | 00:11:37 |
-------------------------------------------------------------------------

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

   3 - access("GCR_DEALS"."GCR_DEALS_ID"="GCR_ITEMS"."GCR_DEALS_ID")
   4 - filter("GCR_DEALS"."BU_ID"=TO_NUMBER(:P0_BU_ID))
   5 - access(ROWID=ROWID)
   6 - access("GCR_DEALS"."BU_ID"=TO_NUMBER(:P0_BU_ID))
   8 - filter(DECODE(:P55_DIRECT,'ALL','Y',TRIM(UPPER("NA_ORG_OWNER_EMAI
              L")))=DECODE(:P55_DIRECT,'ALL','Y',TRIM(UPPER(:P55_DIRECT))))

【问题讨论】:

  • 能否请您发布执行计划和表上的索引?
  • 如果不知道表上存在哪些索引并且不查看执行计划,就不可能猜测发生了什么。请编辑您的问题并包括:A)两个表的 DDL,B)两个表上的所有索引,C)执行计划,以及 D)每个表中的行数。谢谢。
  • gcr_items 仅包含 7386 行 - 我怀疑索引访问是否真的会提高性能,因为显然您正在从该表中选择所有行

标签: sql oracle performance indexing


【解决方案1】:

在开始时,WHERE 子句中的部分条件必须在不使用decode 函数的情况下分解(或“反编译”-或“重新生成”)为更简单的形式,查询优化器可以理解这种形式:

AND
decode(:P55_DIRECT,'ALL','Y',trim(upper(NA_ORG_OWNER_EMAIL)))=
decode(:P55_DIRECT,'ALL','Y',trim(upper(:P55_DIRECT))) 

进入:

AND (
    :P55_DIRECT = 'ALL'
    OR
    trim(upper(:P55_DIRECT)) = trimm(upper(NA_ORG_OWNER_EMAIL))
)

为了根据存储在索引中的值查找表中的行,Oracle 使用了一种名为索引扫描的访问方法,详情请参阅此链接:
@ 987654321@

最常见的访问方法之一是索引范围扫描,请参见此处:
https://docs.oracle.com/cd/B19306_01/server.102/b14211/optimops.htm#i45075

文档说(在后一个链接中):

优化器在找到一个或多个前导时使用范围扫描 条件中指定的索引的列,例如:

col1 = :b1

col1

col1 > :b1

前面的条件的AND组合 索引

col1 like 'ASD%' 通配符搜索不应位于前导 位置,否则条件 col1 像 '%ASD' 不会导致 范围扫描。

以上意味着优化器能够使用索引仅查找包含基本比较运算符的查询条件的行:= < > <= >= LIKE,用于将简单值与普通列名进行比较

文档没有明确说明的内容 - 您需要在两行之间推断出它 - 事实是,当在条件中使用某些函数时,以 function( column_name )function( expression_involving_column_names ) 的形式,那么不能使用索引范围扫描
在这种情况下,查询优化器必须为表中的每一行单独评估此表达式,因此必须读取所有行(执行全表扫描)。


一个简短的结论和经验法则:

WHERE 子句中的函数可以阻止优化器使用 索引

如果您在 WHERE 子句中的某处看到某个函数,则表明您是 闯红灯
立即停止,三思而后行 此功能会影响查询优化器和您的性能 查询,并尝试将条件重写为优化器的形式 能够理解。


现在看看我们重写的条件:

AND (
    :P55_DIRECT = 'ALL'
    OR
    trim(upper(:P55_DIRECT)) = trimm(upper(NA_ORG_OWNER_EMAIL))
)

STOP - 仍然有两个函数 trimupper 应用于名为 NA_ORG_OWNER_EMAIL 的列。我们需要考虑它们如何影响查询优化器。

我假设您已在单个列上创建了一个普通索引:
CREATE INDEX somename ON GCR_ITEMS( NA_ORG_OWNER_EMAIL )
如果是,那么该索引仅包含 NA_ORG_OWNER_EMAIL 的普通值。
但是查询试图找到trimm(upper(NA_ORG_OWNER_EMAIL))的值,这些值没有存储在索引中,所以在这种情况下不能使用这个索引。

这种情况需要基于函数的索引
https://docs.oracle.com/cd/E11882_01/appdev.112/e41502/adfns_indexes.htm#ADFNS00505

CREATE INDEX somename ON GCR_ITEMS( trim( upper( NA_ORG_OWNER_EMAIL )))


不幸的是,即使基于函数的索引也无济于事,因为查询中的条件过于笼统 - 如果值为 :P55_DIRECT = ALL,则查询必须从表中检索所有行(执行全表扫描),否则必须使用索引在其中搜索值。

这是因为查询优化器仅在第一次执行期间计划(将其视为“已编译”)一次。然后计划存储在缓存中并用于执行查询以供所有进一步的执行。参数的一个值是事先不知道的,所以计划必须考虑每一种可能的情况,因此总是会执行全表扫描。
在 12c 中有一个新特性“自适应查询优化”:
https://docs.oracle.com/database/121/TGSQL/tgsql_optcncpt.htm#TGSQL94982
查询优化器在每次运行时分析查询的每个参数,并且能够检测到计划对于某些运行时参数不是最优的,并根据实际参数的值选择更好的“子计划”......但是你必须使用 12c,并额外支付企业版的费用,因为只有此版本包含该功能。而且目前还不确定自适应计划在这种情况下是否有效。

您可以在不支付 12c EE 的情况下将此一般查询分为两个单独的变体,一个用于 :P55_DIRECT = ALL 的情况,另一个用于其余情况,并在客户端(您的应用程序)中运行适当的变体,具体取决于关于这个参数的值。

:P55_DIRECT = ALL 的版本,将执行全表扫描

where gcr_deals.GCR_DEALS_ID=gcr_items.GCR_DEALS_ID 
and 
gcr_deals.bu_id=:P0_BU_ID
order by 1

还有一个用于其他情况的版本,它将使用基于函数的索引:

where gcr_deals.GCR_DEALS_ID=gcr_items.GCR_DEALS_ID 
and 
gcr_deals.bu_id=:P0_BU_ID
and 
 trim(upper(:P55_DIRECT)) = trimm(upper(NA_ORG_OWNER_EMAIL))
order by 1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-02
    • 2018-12-09
    • 1970-01-01
    • 1970-01-01
    • 2018-07-13
    相关资源
    最近更新 更多