如何在 Oracle 10+ 中包含 NULL 的列上使用基于函数的索引?
Posted
技术标签:
【中文标题】如何在 Oracle 10+ 中包含 NULL 的列上使用基于函数的索引?【英文标题】:How to use a function-based index on a column that contains NULLs in Oracle 10+? 【发布时间】:2008-10-07 04:33:41 【问题描述】:假设您在 Oracle 中有一个表:
CREATE TABLE person (
id NUMBER PRIMARY KEY,
given_names VARCHAR2(50),
surname VARCHAR2(50)
);
使用这些基于函数的索引:
CREATE INDEX idx_person_upper_given_names ON person (UPPER(given_names));
CREATE INDEX idx_person_upper_last_name ON person (UPPER(last_name));
现在, given_names 没有 NULL 值,但为了参数的缘故 last_name 有。如果我这样做:
SELECT * FROM person WHERE UPPER(given_names) LIKE 'P%'
解释计划告诉我它使用索引,但将其更改为:
SELECT * FROM person WHERE UPPER(last_name) LIKE 'P%'
它没有。 Oracle 文档说,只有在满足几个条件时才会使用基于函数的索引,其中之一是确保没有 NULL 值,因为它们没有被索引。
我已经尝试过这些查询:
SELECT * FROM person WHERE UPPER(last_name) LIKE 'P%' AND UPPER(last_name) IS NOT NULL
和
SELECT * FROM person WHERE UPPER(last_name) LIKE 'P%' AND last_name IS NOT NULL
在后一种情况下,我什至在 last_name 上添加了一个索引,但无论我尝试什么,它都使用全表扫描。假设我无法摆脱 NULL 值,我如何让这个查询使用 UPPER(last_name) 上的索引?
【问题讨论】:
表中实际有多少行?您能否发布全表扫描的解释计划以及何时选择使用索引(出于练习的目的,您可能必须提示它或将列更改为 NOT NULL)。 【参考方案1】:可以使用索引,尽管优化器可能选择不将它用于您的特定示例:
SQL> create table my_objects
2 as select object_id, object_name
3 from all_objects;
Table created.
SQL> select count(*) from my_objects;
2 /
COUNT(*)
----------
83783
SQL> alter table my_objects modify object_name null;
Table altered.
SQL> update my_objects
2 set object_name=null
3 where object_name like 'T%';
1305 rows updated.
SQL> create index my_objects_name on my_objects (lower(object_name));
Index created.
SQL> set autotrace traceonly
SQL> select * from my_objects
2 where lower(object_name) like 'emp%';
29 rows selected.
Execution Plan
----------------------------------------------------------
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 510 | 355 (1)|
| 1 | TABLE ACCESS BY INDEX ROWID| MY_OBJECTS | 17 | 510 | 355 (1)|
|* 2 | INDEX RANGE SCAN | MY_OBJECTS_NAME | 671 | | 6 (0)|
------------------------------------------------------------------------------------
您阅读的文档可能指出,就像任何其他索引一样,全空键不存储在索引中。
【讨论】:
【参考方案2】:在您的示例中,您创建了两次相同的索引 - 这会产生错误,因此我假设这是粘贴错误,而不是您尝试的实际代码。
我试过了
CREATE INDEX idx_person_upper_surname ON person (UPPER(surname));
SELECT * FROM person WHERE UPPER(surname) LIKE 'P%';
它产生了预期的查询计划:
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=67)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'PERSON' (TABLE) (Cost=1
Card=1 Bytes=67)
2 1 INDEX (RANGE SCAN) OF 'IDX_PERSON_UPPER_SURNAME' (INDEX)
(Cost=1 Card=1)
要回答您的问题,是的,它应该可以工作。尝试仔细检查您是否正确创建了第二个索引。
同时尝试一个明确的提示:
SELECT /*+INDEX(PERSON IDX_PERSON_UPPER_SURNAME)*/ *
FROM person
WHERE UPPER(surname) LIKE 'P%';
如果这有效,但只有提示,那么它可能与 CBO 统计信息出错或与 CBO 相关的 init 参数有关。
【讨论】:
您是否将 NULL 放入表中并获取该查询计划?【参考方案3】:您确定要使用索引吗?全表扫描还不错。根据表的大小,进行表扫描可能比使用索引更有效。它还取决于数据的密度和分布,这就是收集统计数据的原因。通常可以信任基于成本的优化器做出正确的选择。除非您有特定的性能问题,否则我不会太担心。
【讨论】:
【参考方案4】:Oracle 仍将使用基于函数的索引,其列包含 null - 我认为您误解了文档。
如果你想检查这个,你需要在函数索引中放一个 nvl。
类似...
create index idx_person_upper_surname on person (nvl(upper(surname),'N/A'));
然后您可以使用索引查询
select * from person where nvl(upper(surname),'N/A') = 'PIERPOINT'
虽然,都有些丑陋。由于大多数人都有姓氏,也许“不为空”是合适的:-)。
【讨论】:
【参考方案5】:您可以通过基于文字值的索引来规避空值在这种情况或其他情况下未索引的问题:
CREATE INDEX idx_person_upper_surname ON person (UPPER(surname),0);
这允许您将索引用于以下查询:
Select *
From person
Where UPPER(surname) is null;
此查询通常不会使用索引,但位图索引或包含除 surname 之外的不可为空的实列的索引除外。
【讨论】:
David,什么样的查询可以使用这样的索引? 任何使用 UPPER(姓氏)的查询。 David 的技巧确保 UPPER(surname) 的空值被索引。如果所有值都为空,Oracle 不会编制索引。字面值 0 确保这种情况永远不会发生。以上是关于如何在 Oracle 10+ 中包含 NULL 的列上使用基于函数的索引?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用SQL更新VBA Access中包含NULL值的列?