Oracle like 语句未使用正确的索引

Posted

技术标签:

【中文标题】Oracle like 语句未使用正确的索引【英文标题】:Oracle like statement not using correct index 【发布时间】:2018-07-12 06:25:22 【问题描述】:

Oracle 数据库。

我有以下 SQL 段,它正在对 PROVIDER P1 表执行全表扫描。我相信这是因为它动态地构建了一个like 子句,正如您在第 XXX 行看到的那样。

我在 PROVIDER.TERMINAL_NUMBER 上有一个索引,并且以下 SQL sn-p 确实使用了正确的索引。

select * from providers where terminal_number like '1234%'

那么为什么以下内容没有达到该索引?

SELECT P1.PROVIDER_NUMBER, P1.TERMINAL_NUMBER, PC."ORDER" FROM PROVIDERS P1
  INNER JOIN PROVIDER_CONFIG PC
         ON PC.PROVIDER_NUMBER = P1.PROVIDER_NUMBER
WHERE EXISTS (
  SELECT E2.* FROM EQUIPMENT E1
    INNER JOIN EQUIPMENT E2
            ON E1.MERCHANT_NUMBER = E2.MERCHANT_NUMBER
  WHERE E1.TERMINAL_NUMBER = 'SA323F'
  AND E1.STATUS IN (0, 9)
  AND E2.STATUS IN (0, 9)
  XXX 
  AND P1.TERMINAL_NUMBER LIKE SUBSTR(E2.TERMINAL_NUMBER, 0, length(E2.TERMINAL_NUMBER) - 1) || '%'
)
ORDER BY PC."ORDER" DESC

【问题讨论】:

“我相信这是因为它动态构建了一个 like 子句” - 事实并非如此。 【参考方案1】:

这里...

 select * from providers where terminal_number like '1234%'

... Optimiser 知道所有拟合数字都以固定前缀开头,因此将位于索引中。因此,读取索引可能非常有效。

但是这里没有这方面的知识……

P1.TERMINAL_NUMBER LIKE SUBSTR(E2.TERMINAL_NUMBER, 0, length(E2.TERMINAL_NUMBER) - 1) || '%'

E2.TERMINAL_NUMBER 可以有任意数量的不同前缀,查询将返回来自PROVIDERS 表的所有记录。所以索引读取的效率会非常低,而直接的全扫描方法是正确的选择。

可能可以重写查询,使其更有效地工作 - 例如,您需要快速全索引扫描而不是全表扫描。但在不了解您的数据和业务规则的情况下,我们无法真正提供帮助,尤其是在涉及动态查询生成时。

可能会提高性能的一件事是将 WHERE EXISTS 替换为 WHERE IN...

SELECT P1.PROVIDER_NUMBER, P1.TERMINAL_NUMBER, PC."ORDER" FROM PROVIDERS P1
  INNER JOIN PROVIDER_CONFIG PC
         ON PC.PROVIDER_NUMBER = P1.PROVIDER_NUMBER
WHERE substr(P1.TERMINAL_NUMBER, 1, 5) IN  (
  SELECT SUBSTR(E2.TERMINAL_NUMBER, 1, 5)
    FROM EQUIPMENT E1
    INNER JOIN EQUIPMENT E2
            ON E1.MERCHANT_NUMBER = E2.MERCHANT_NUMBER
  WHERE E1.TERMINAL_NUMBER = 'SA323F'
  AND E1.STATUS IN (0, 9)
  AND E2.STATUS IN (0, 9)
)
ORDER BY PC."ORDER" DESC

如果终端编号的长度是恒定的,这将起作用。只有你知道你的数据,所以只有你能判断它是否会飞。

【讨论】:

SELECT null 不会使查询比 SELECT e2.* 快一点,这对于 Oracle(和许多其他 DBMS)来说是一个从未出现过的性能神话【参考方案2】:

如果此查询不使用索引:

select *
from providers
where terminal_number like '1234%'

那么大概terminal_number 是数字而不是字符串。类型转换阻止了索引的使用。

如果要使用索引,则将值转换为字符串并使用字符串索引:

create index idx_providers_terminal_number_str on providers(cast(terminal_number as varchar2(255)));

然后将查询写成:

select *
from providers
where cast(terminal_number as varchar2(255)) like '1234%'

【讨论】:

以上是关于Oracle like 语句未使用正确的索引的主要内容,如果未能解决你的问题,请参考以下文章

ORACLE 快速查询数据SQL语句

如何提高oracle模糊查询的性能?

关于Oracle中索引的使用

七 正确使用索引-notes

Oracle - 未使用大表上的索引

求助oracle like%.%模糊查询优化