1

DETERMINISTIC现在,当我使用运算符右侧的函数时,我遇到了一个非常棘手的问题,即 Oracle 执行计划运行严重LIKE。这是我的情况:

情况

我认为执行这样的查询是明智的(简化):

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter(?)

我会绑定?到类似'Eder%'. 现在customersaddresses是非常大的桌子。这就是为什么使用索引很重要的原因。当然,在addresses.cust_id. 但我还在 上创建了一个基于函数的索引special_char_filter(customers.surname),效果非常好。

麻烦

问题是,上述涉及like子句的查询创建了带有 FULL TABLE SCANS on 的执行计划addresses。看起来此查询中的某些内容使 Oracle 无法在addresses.cust_id.

解决方法

我发现,我的问题的解决方案是这样的:

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like ?

DETERMINISTIC从 like 运算符的右侧删除了 (!) 函数,并在 Java 中预先计算了绑定变量。现在这个查询是超快的,没有任何 FULL TABLE SCANS。这也非常快(虽然不等价):

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) = special_char_filter(?)

混乱

我不明白这一点。在运算符右侧设置确定性函数有什么问题like?我在 Oracle 11.2.0.1.0 中观察到了这一点

4

3 回答 3

2

查询中可能什么都没有。基于成本的优化器可能会感到困惑,并认为 FULL TABLE SCAN 更快。您是否尝试过在查询中使用 HINT,强制 Oracle 使用您的索引?

于 2011-03-17T16:19:16.870 回答
2

问题是 Oracle 不知道“special_char_filter(?)”会返回什么。如果它返回一个'%',那么使用索引会很慢,因为一切都会匹配。如果它返回“A%”,它可能也会很慢,因为(假设所有字母的分布相等)大约 4% 的行会匹配。如果它返回一个 '%FRED%',它不会返回很多行,但是使用索引范围扫描的性能会很差,因为这些行可能位于索引的开头、中间或结尾,所以它必须做整个索引。

如果您知道 special_char_filter 将始终返回一个开头至少包含三个“实心”字符的字符串,那么您可能会遇到更好的运气

SELECT [...] FROM customers cust JOIN address addr ON addr.cust_id = cust.id WHERE special_char_filter(cust.surname) like special_char_filter(?) AND substr(special_char_filter(cust.surname),1,3) = substr(special_char_filter (?),1,3)

在 substr(special_char_filter(cust.surname),1,3) 上有一个 FBI

尽管如果在 java 中预先计算结果有效,那么请坚持下去。

除此之外,我可能会查看 Oracle Text 的匹配项。

于 2011-03-17T23:12:59.327 回答
1

下面的脚本显示了我用来对 ADDRESSES 索引进行索引范围扫描的步骤。在查看细节之前,您可能只想运行整个程序。如果您没有对最后两个查询进行两次索引范围扫描,那么这可能是我们的版本、设置等方面的差异。我使用的是 10.2.0.1.0。

如果您确实看到了想要的计划,那么您可能希望逐步修改我的脚本以使其更准确地反映真实数据,并尝试找到使其崩溃的确切更改。希望我的设置至少接近真实情况,并且不会遗漏任何与您的确切问题无关的细节。

这是一个奇怪的问题,我不明白这里发生的一切。例如,我不知道为什么 use_nl 有效,但索引提示无效。

(请注意,我的执行时间是基于重复执行的。第一次运行时,一些查询可能会更慢,因为数据没有被缓存。)

--create tables
create table customers (id number, surname varchar2(100), other varchar2(100));
create table addresses (cust_id number, other varchar2(100));

--create data and indexes
insert into customers select level, 'ASDF'||level, level from dual connect by level <= 1000000;
insert into addresses select level, level from dual connect by level <= 1000000;
create index customers_id on customers(id);
create index addresses_cust_id on addresses(cust_id);
create index customers_special_char_filter on customers(special_char_filter(surname));

--create function
create or replace function special_char_filter(surname in varchar) return varchar2 deterministic is
begin
    return replace(surname, 'bad value!', null);
end;
/

--gather stats
begin
    dbms_stats.gather_table_stats(ownname => user, tabname => 'CUSTOMERS', cascade => true);
    dbms_stats.gather_table_stats(ownname => user, tabname => 'ADDRESSES', cascade => true);
end;
/

set autotrace on;

--Index range scan on CUSTOMERS_SPECIAL_CHAR_FILTER, but full table scan on ADDRESSES
--(0.2 seconds)
SELECT *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('ASDF100000bad value!%');

--This uses the addresses index but it does an index full scan.  Not really what we want.
--I'm not sure why I can't get an index range scan here.
--Various other index hints also failed here.  For example, no_index_ffs won't stop an index full scan.
--(1 second)
SELECT /*+ index(addr addresses_cust_id) */ *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('ASDF100000bad value!%');


--Success!  With this hint both indexes are used and it's super-fast.
--(0.02 seconds)
SELECT /*+ use_nl(cust addr) */ *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('ASDF100000bad value!%');


--But forcing the index won't always be a good idea, for example when the value starts with '%'.
--(1.2 seconds)
SELECT /*+ use_nl(cust addr) */ *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('%ASDF100000bad value!%');
于 2011-03-19T04:42:22.570 回答