当 SQL 包含带 LIKE 的参数时不使用 Oracle 语言索引
Oracle linguistic index not used when SQL contains parameter with LIKE
我的架构(简体):
CREATE TABLE LOC
(
LOC_ID NUMBER(15,0) NOT NULL,
LOC_REF_NO VARCHAR2(100 CHAR) NOT NULL
)
/
CREATE INDEX LOC_REF_NO_IDX ON LOC
(
NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''') ASC
)
/
我的查询(在 SQL*Plus 中):
ALTER SESSION SET NLS_COMP=LINGUISTIC NLS_SORT=BINARY_AI
/
VAR LOC_REF_NO VARCHAR2(50)
BEGIN
:LOC_REF_NO := 'SPDJ1501270';
END;
/
-- Causes full table scan (i.e, does not use LOC_REF_NO_IDX)
SELECT * FROM LOC WHERE LOC_REF_NO LIKE :LOC_REF_NO||'%';
-- Causes index scan (i.e. uses LOC_REF_NO_IDX)
SELECT * FROM LOC WHERE LOC_REF_NO LIKE 'SPDJ1501270%';
未使用索引已通过执行 AUTOTRACE(EXPLAIN PLAN)确认,SQL 只是运行速度较慢。尝试了一些没有成功的事情。任何人都知道发生了什么事?我正在使用 Oracle 数据库 11g 企业版 11.2.0.3.0 版 - 64 位。
更新 1:
请注意,当我使用带有参数的等号时使用索引:
SELECT * FROM LOC WHERE LOC_REF_NO = :LOC_REF_NO;
解释计划:
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 93 | 5 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| LOC | 1 | 93 | 5 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | LOC_REF_NO_IDX | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''')=NLSSORT(:LOC_REF_NO,'nls_
sort=''BINARY_AI'''))
鉴于
SELECT * FROM LOC WHERE LOC_REF_NO LIKE :LOC_REF_NO||'%';
解释计划:
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50068 | 3471K| 5724 (1)| 00:01:09 |
|* 1 | TABLE ACCESS FULL| LOC | 50068 | 3471K| 5724 (1)| 00:01:09 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("LOC_REF_NO" LIKE :LOC_REF_NO||'%')
傻眼了!
更新 2:
我们在索引上使用 NLSSORT 的原因是使 Oracle 查询不区分大小写,这是一般建议。以前我们使用 NLS_UPPER 的函数索引。奇怪的是索引总是被使用,参数与否,如下所示。
所以如果 table 如上,LOC_REF_NO_IDX 索引被删除并添加了这个:
CREATE INDEX LOC_REF_NO_CI_IDX ON LOC
(
NLS_UPPER(LOC_REF_NO) ASC
)
/
以下全部使用索引:
ALTER SESSION SET NLS_COMP=BINARY NLS_SORT=BINARY;
SELECT * FROM LOC WHERE NLS_UPPER(LOC_REF_NO) LIKE :LOC_REF_NO||'%';
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50068 | 5329K| 5700 (1)| 00:01:09 |
| 1 | TABLE ACCESS BY INDEX ROWID| LOC | 50068 | 5329K| 5700 (1)| 00:01:09 |
|* 2 | INDEX RANGE SCAN | LOC_REF_NO_CI_IDX | 9012 | | 43 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLS_UPPER("LOC_REF_NO") LIKE :LOC_REF_NO||'%')
filter(NLS_UPPER("LOC_REF_NO") LIKE :LOC_REF_NO||'%')
因此,出于某种原因,当在语言索引上使用带有参数的 LIKE 时,Oracle 优化器决定不使用该索引。
根据 Oracle 支持说明 1451804.1,这是将 LIKE
与基于 NLSSORT
的索引一起使用的已知限制。
如果您查看固定值查询的执行计划,您会看到如下内容:
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''')>=HEXTORAW('7370646A313530
3132373000') AND NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''')<HEXTORAW('7370646A313
5303132373100') )
这些原始值的计算结果为 spdj1501270
和 spdj1501271
;这些是从您的常量字符串派生的,并且任何符合您喜欢的条件的值都将在该范围内。该解析时转换必须基于常量值,并且不适用于绑定变量或表达式,大概是因为它求值太晚了。
有关详细信息,请参阅说明,但遗憾的是似乎没有解决方法。您可能不得不回到您的 NLS_UPPER
方法。
前面的解释一般适用,但不适用于这种特定情况,但保留以供参考...
一般来说,使用固定值,优化器在解析查询时可以估计您的查询的选择性,因为它可以大致知道索引值与该值匹配的比例。它可能会或可能不会使用索引,具体取决于您使用的实际值。
使用绑定变量,它通过 bind variable peeking:
提出了一个计划
In bind variable peeking (also known as bind peeking), the optimizer looks at the value in a bind variable when the database performs a hard parse of a statement.
When a query uses literals, the optimizer can use the literal values to find the best plan. However, when a query uses bind variables, the optimizer must select the best plan without the presence of literals in the SQL text. This task can be extremely difficult. By peeking at bind values the optimizer can determine the selectivity of a WHERE clause condition as if literals had been used, thereby improving the plan.
它使用收集到的统计数据来决定某个特定值是否比其他值更有可能。这里可能不会是这种情况,尤其是 like
。它正在回退到完整的 table 扫描,因为它无法确定何时进行硬解析以确保索引在大多数情况下更具选择性。想象一下,例如,解析决定使用索引,但随后您提供的绑定值仅为 S
,甚至为 null - 使用索引将比完整的 table 做更多的工作扫描.
另外值得注意的是:
When choosing a plan, the optimizer only peeks at the bind value during the hard parse. This plan may not be optimal for all possible values.
Adaptive cursor sharing 可以缓解这种情况,但此查询可能不合格:
The criteria used by the optimizer to decide whether a cursor is bind-sensitive include the following:
The optimizer has peeked at the bind values to generate selectivity estimates.
A histogram exists on the column containing the bind value.
当我用少量有限数据对此进行模拟时,v$sql
将 is_bind_sensitive
和 is_bind_aware
报告为 'N'
。
我的架构(简体):
CREATE TABLE LOC
(
LOC_ID NUMBER(15,0) NOT NULL,
LOC_REF_NO VARCHAR2(100 CHAR) NOT NULL
)
/
CREATE INDEX LOC_REF_NO_IDX ON LOC
(
NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''') ASC
)
/
我的查询(在 SQL*Plus 中):
ALTER SESSION SET NLS_COMP=LINGUISTIC NLS_SORT=BINARY_AI
/
VAR LOC_REF_NO VARCHAR2(50)
BEGIN
:LOC_REF_NO := 'SPDJ1501270';
END;
/
-- Causes full table scan (i.e, does not use LOC_REF_NO_IDX)
SELECT * FROM LOC WHERE LOC_REF_NO LIKE :LOC_REF_NO||'%';
-- Causes index scan (i.e. uses LOC_REF_NO_IDX)
SELECT * FROM LOC WHERE LOC_REF_NO LIKE 'SPDJ1501270%';
未使用索引已通过执行 AUTOTRACE(EXPLAIN PLAN)确认,SQL 只是运行速度较慢。尝试了一些没有成功的事情。任何人都知道发生了什么事?我正在使用 Oracle 数据库 11g 企业版 11.2.0.3.0 版 - 64 位。
更新 1:
请注意,当我使用带有参数的等号时使用索引:
SELECT * FROM LOC WHERE LOC_REF_NO = :LOC_REF_NO;
解释计划:
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 93 | 5 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| LOC | 1 | 93 | 5 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | LOC_REF_NO_IDX | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''')=NLSSORT(:LOC_REF_NO,'nls_
sort=''BINARY_AI'''))
鉴于
SELECT * FROM LOC WHERE LOC_REF_NO LIKE :LOC_REF_NO||'%';
解释计划:
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50068 | 3471K| 5724 (1)| 00:01:09 |
|* 1 | TABLE ACCESS FULL| LOC | 50068 | 3471K| 5724 (1)| 00:01:09 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("LOC_REF_NO" LIKE :LOC_REF_NO||'%')
傻眼了!
更新 2:
我们在索引上使用 NLSSORT 的原因是使 Oracle 查询不区分大小写,这是一般建议。以前我们使用 NLS_UPPER 的函数索引。奇怪的是索引总是被使用,参数与否,如下所示。
所以如果 table 如上,LOC_REF_NO_IDX 索引被删除并添加了这个:
CREATE INDEX LOC_REF_NO_CI_IDX ON LOC
(
NLS_UPPER(LOC_REF_NO) ASC
)
/
以下全部使用索引:
ALTER SESSION SET NLS_COMP=BINARY NLS_SORT=BINARY;
SELECT * FROM LOC WHERE NLS_UPPER(LOC_REF_NO) LIKE :LOC_REF_NO||'%';
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50068 | 5329K| 5700 (1)| 00:01:09 |
| 1 | TABLE ACCESS BY INDEX ROWID| LOC | 50068 | 5329K| 5700 (1)| 00:01:09 |
|* 2 | INDEX RANGE SCAN | LOC_REF_NO_CI_IDX | 9012 | | 43 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLS_UPPER("LOC_REF_NO") LIKE :LOC_REF_NO||'%')
filter(NLS_UPPER("LOC_REF_NO") LIKE :LOC_REF_NO||'%')
因此,出于某种原因,当在语言索引上使用带有参数的 LIKE 时,Oracle 优化器决定不使用该索引。
根据 Oracle 支持说明 1451804.1,这是将 LIKE
与基于 NLSSORT
的索引一起使用的已知限制。
如果您查看固定值查询的执行计划,您会看到如下内容:
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''')>=HEXTORAW('7370646A313530
3132373000') AND NLSSORT("LOC_REF_NO",'nls_sort=''BINARY_AI''')<HEXTORAW('7370646A313
5303132373100') )
这些原始值的计算结果为 spdj1501270
和 spdj1501271
;这些是从您的常量字符串派生的,并且任何符合您喜欢的条件的值都将在该范围内。该解析时转换必须基于常量值,并且不适用于绑定变量或表达式,大概是因为它求值太晚了。
有关详细信息,请参阅说明,但遗憾的是似乎没有解决方法。您可能不得不回到您的 NLS_UPPER
方法。
前面的解释一般适用,但不适用于这种特定情况,但保留以供参考...
一般来说,使用固定值,优化器在解析查询时可以估计您的查询的选择性,因为它可以大致知道索引值与该值匹配的比例。它可能会或可能不会使用索引,具体取决于您使用的实际值。
使用绑定变量,它通过 bind variable peeking:
提出了一个计划In bind variable peeking (also known as bind peeking), the optimizer looks at the value in a bind variable when the database performs a hard parse of a statement.
When a query uses literals, the optimizer can use the literal values to find the best plan. However, when a query uses bind variables, the optimizer must select the best plan without the presence of literals in the SQL text. This task can be extremely difficult. By peeking at bind values the optimizer can determine the selectivity of a WHERE clause condition as if literals had been used, thereby improving the plan.
它使用收集到的统计数据来决定某个特定值是否比其他值更有可能。这里可能不会是这种情况,尤其是 like
。它正在回退到完整的 table 扫描,因为它无法确定何时进行硬解析以确保索引在大多数情况下更具选择性。想象一下,例如,解析决定使用索引,但随后您提供的绑定值仅为 S
,甚至为 null - 使用索引将比完整的 table 做更多的工作扫描.
另外值得注意的是:
When choosing a plan, the optimizer only peeks at the bind value during the hard parse. This plan may not be optimal for all possible values.
Adaptive cursor sharing 可以缓解这种情况,但此查询可能不合格:
The criteria used by the optimizer to decide whether a cursor is bind-sensitive include the following:
The optimizer has peeked at the bind values to generate selectivity estimates.
A histogram exists on the column containing the bind value.
当我用少量有限数据对此进行模拟时,v$sql
将 is_bind_sensitive
和 is_bind_aware
报告为 'N'
。