使用具有确定性函数的LIKE运算符时Oracle执行计划

使用具有确定性函数的LIKE运算符时Oracle执行计划,oracle,sql-execution-plan,sql-like,deterministic,Oracle,Sql Execution Plan,Sql Like,Deterministic,现在,当我在LIKE操作符的右侧使用DETERMINISTIC函数时,我遇到了一个Oracle执行计划运行混乱的棘手问题。这就是我的情况: 形势 我认为执行这样的查询(简化)是明智的: 我会将?绑定到类似'Eder%'的东西上。现在客户和地址都是非常大的表。这就是为什么使用索引很重要。当然,addresses.cust\u id上有一个常规索引。但是,我还创建了一个基于特殊字符过滤器(customers.nam姓氏)的函数索引,它运行得非常好 麻烦 问题是,上面的查询涉及一个like子句,它创建

现在,当我在
LIKE
操作符的右侧使用
DETERMINISTIC
函数时,我遇到了一个Oracle执行计划运行混乱的棘手问题。这就是我的情况:

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

我会将
绑定到类似
'Eder%'
的东西上。现在
客户
地址
都是非常大的表。这就是为什么使用索引很重要。当然,
addresses.cust\u id
上有一个常规索引。但是,我还创建了一个基于
特殊字符过滤器(customers.nam姓氏)
的函数索引,它运行得非常好

麻烦 问题是,上面的查询涉及一个
like
子句,它创建了执行计划,并对
地址进行了完整的表扫描。这个查询中似乎有什么东西阻止Oracle在
addresses.cust\u id
上使用索引

解决办法 我发现,解决我问题的办法是:

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like ?
我从like操作符的右侧删除了(
DETERMINISTIC
!)函数,并用Java预先计算了bind变量。现在这个查询速度非常快,没有任何完整的表扫描。这也是非常快的(尽管不是等效的):

混乱
我不明白。将确定性函数放在
like
操作符的右侧有什么不对?我在Oracle 11.2.0.1.0中观察到了这一点,它可能根本不在查询中。基于成本的优化器可能会感到困惑,认为全表扫描更快。您是否尝试过在查询中使用提示,强制Oracle使用您的索引?

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

问题是Oracle不知道“特殊字符过滤器(?)将返回什么。如果它返回“%”,那么使用索引将非常慢,因为所有内容都将匹配。如果它返回“a%”,它可能也会很慢,因为(假设所有字母的分布相等)大约有4%的行会匹配。如果它返回“%FRED%”,它不会返回很多行,但是使用索引范围扫描会执行得很差,因为行可能位于索引的开始、中间或结尾,因此它必须执行整个索引

如果您知道特殊的_char_过滤器总是返回一个开头至少有三个“实心”字符的字符串,那么您可能会更幸运地使用它

选择[…] 来自客户 加入地址addr ON addr.cust\u id=cust.id 其中特殊字符过滤器(客户姓氏)与特殊字符过滤器(?) 和substr(特殊字符过滤器(客户姓氏),1,3)=substr(特殊字符过滤器(?),1,3)

在substr上有FBI(特殊字符过滤器(客户姓氏),1,3)

虽然如果在java中预先计算结果有效,那么请坚持使用它


除此之外,我可能会查看Oracle文本以查找匹配项。

问题是Oracle不知道“特殊字符过滤器(?)”将返回什么。如果它返回“%”,那么使用索引将非常慢,因为所有内容都将匹配。如果它返回“a%”,它可能也会很慢,因为(假设所有字母的分布相等)大约有4%的行会匹配。如果它返回“%FRED%”,它不会返回很多行,但是使用索引范围扫描会执行得很差,因为行可能位于索引的开始、中间或结尾,因此它必须执行整个索引

如果您知道特殊的_char_过滤器总是返回一个开头至少有三个“实心”字符的字符串,那么您可能会更幸运地使用它

选择[…] 来自客户 加入地址addr ON addr.cust\u id=cust.id 其中特殊字符过滤器(客户姓氏)与特殊字符过滤器(?) 和substr(特殊字符过滤器(客户姓氏),1,3)=substr(特殊字符过滤器(?),1,3)

在substr上有FBI(特殊字符过滤器(客户姓氏),1,3)

虽然如果在java中预先计算结果有效,那么请坚持使用它


除此之外,我可能会查看Oracle文本以查找匹配项。

下面的脚本显示了我用于在地址索引上进行索引范围扫描的步骤。在查看详细信息之前,您可能希望运行整个过程。如果你没有得到两次索引范围扫描 对于最后两个查询,我们的版本、设置等可能有所不同。我使用的是10.2.0.1.0

如果您确实看到了所需的计划,那么您可能希望逐步修改我的脚本,使其更准确地反映真实数据,并尝试找到使其崩溃的确切更改。希望我的设置至少是接近真实的东西,并且没有遗漏任何细节 这与你的确切问题无关

这是一个奇怪的问题,我不理解这里发生的一切。例如,我不知道为什么使用\u nl有效,但索引提示无效

(请注意,我的执行时间基于重复执行。第一次运行此命令时,某些查询可能会较慢,因为数据未缓存。)

——创建表
创建表客户(id号,姓氏varchar2(100),其他varchar2(100));
创建表地址(客户id号,其他varchar2(100));
--创建数据和索引
插入客户选择级别“ASDF”| |级别,按级别“客户”从双连接中选择级别,级联=>true);
dbms_stats.gather_table_stats(ownname=>user,tabname=>ADDRESSES,cascade=>true);
终止
/
设置自动追踪;
--客户上的索引范围扫描\u特殊\u字符\u过滤器,但地址上的全表扫描
--(0.2秒)
挑选*
来自客户
加入地址addr ON addr.cust\u id=cust.id
其中特殊字符过滤器(客户姓氏)与特殊字符过滤器类似('ASDF10000BAD value
SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like ?
SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) = special_char_filter(?)
--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!%');