文档内容
适用于: Oracle Database - Standard Edition - 版本 8.1.7.4 和更高版本
Oracle Database - Personal Edition - 版本 8.1.7.4 和更高版本
Oracle Database - Enterprise Edition - 版本 8.1.7.4 和更高版本
本文档所含信息适用于所有平台
用途
这篇文章用来解答下面的问题:为什么我的索引没有被使用?
排错步骤
“为什么索引没有被使用”是一个涉及面较广的问题。有很多种原因会导致索引没有被使用。下面是一些非常有用的检查列表。请点击下面链接来查看文章的具体内容:
快速检查
索引本身的问题
-
索引列或者索引的前置列是否在单表(non-join)查询的 Where 条件中(predicate list)? 如果不是,至少需要索引前置列在查询谓词列表中,查询才能使用索引。(例外:请见下面的 Skip Scan)。 示例: 在列 EMP.EMPNO 上定义了单列索引 EMPNO_I1,同时在列 EMP.EMPNO 和 EMP.DEPT 上定义了联合索引 EMPNO_DEPT_I2(EMP.EMPNO为索引前置列)。那么必须在查询谓词列表中(where从句)使用列 EMP.EMPNO,优化器才能使用这两个索引中的某一个。
SELECT ename, sal, deptno FROM emp WHERE empno<100;
例外:
- 只要索引中包含查询所需的所有列, 而且至少有一个索引列中含有非空约束,CBO 就能够使用索引快速全扫描(INDEX_FFS)。执行 INDEX_FFS 不需要索引前置列。需要注意的是 INDEX_FFS 不能保证返回的行是排序的。结果的顺序是与读取索引块的顺序一致的,只有当使用了 'order by' 子句时才能保证结果是排序的。请参照:
Document 344135.1 Ordering of Result Data
Document 70135.1 Index Fast Full Scan Usage To Avoid Full Table Scans
- CBO 能使用 Index Skip Scan (INDEX_SS). 执行 INDEX_SS 不需要索引前置列。请参照:
Document 212391.1 Index Skip Scan Feature
- CBO 能够选用一个索引来避免排序,但是索引列必须存于在 order by 子句中才可以。
请参照
Document 67409.1 When will an ORDER BY use an Index to Avoid Sorting?
Document 10577.1 Driving ORDER BY using an Index
-
索引列是否用在连接谓词中(join predicates)? 例如,下面这个连接谓词定义了如何在表 emp 和 dept 的 deptno 列上做连接:
emp.deptno = dept.deptno
如果索引列是连接谓词的一部分,那么查询在执行时使用了哪种类型的连接?
- 哈希/排序合并连接(Hash / Sort Merge Join): 对于哈希连接和排序合并,在连接执行的时候,外部表的信息还没有获得,因此无法进行对内部表的行检索。它的处理方式是将外部表和内部表分别查询后将结果合并。哈希连接和排序合并的内部表不能通过连接的索引列单独被访问。这是连接类型的执行机制的限制。嵌套循环连接有所不同,它们允许通过索引查询内部表的连接列。
- 嵌套循环连接(Nested Loops Join):嵌套循环连接读取外部表,然后利用所收集的信息访问内部表。该算法允许对内部表基于索引进行查询。
只有嵌套循环连接(Nested loops join)允许索引在内部表中仅基于连接列进行查找。 另外,连接的顺序(join order)是否允许使用索引? 一个嵌套循环连接的外部表必须已经访问过,才可以在内部表中使用索引。查看 explain plan,以确定哪些访问路径已经使用。由于这个限制,表的连接顺序是很重要的。 例如:如果我们通过"emp.deptno = dept.deptno"来对 EMP 和 DEPT 做连接,并且在 EMP.DEPTNO 有一个索引,并假设查询中没有与 EMP.DEPTNO 相关的其他谓词,EMP 是在 DEPT 前被访问,然后没有值可用于在 EMP.DEPTNO 索引中查询。在这种连接顺序下,要想使用这个索引我们只能使用全索引扫描或索引快速全扫描。在这种情况下,全表扫描(FTS)的成本可能更小。 -
索引列在 IN 或者多个 OR 语句中? 比如:
emp.deptno IN (10,23,34,....)
或
emp.deptno = 10
OR emp.deptno = 23
OR emp.deptno = 34
....
这种情况下查询可能已经被转化为不能使用索引的语句。请参照:
Document 62153.1 Optimization of large inlists/multiple OR`s
-
索引列是否被函数修改? 索引不能用于被函数修改的列。函数索引(function based indexes)可以用来解决这个问题。
Oracle Database Online Documentation 12c Release 1 (12.1) / Database Administration
Database Concepts
Chapter 3 Indexes and Index-Organized Tables
Overview of Function-Based Indexes
http://docs.oracle.com/database/121/CNCPT/indexiot.htm#CBBGIIFB
Oracle Database Online Documentation 12c Release 1 (12.1) / Database Administration
Database Performance Tuning Guide
Chapter 2 Designing and Developing for Performance
Section 2.5.3 Table and Index Design
http://docs.oracle.com/database/121/TGDBA/pfgrf_design.htm#CJHCJIDB
-
隐式类型转换(implicit type conversion)是什么? 如果进行比较的两个值的数据类型不同,则 Oracle 必须将其中一个值进行类型转换使其能够比较。这就是所谓的隐式类型转换。通常当开发人员将数字存储在字符列时会导致这种问题的产生。Oracle 在运行时会强制转化其中一个值,(由于固定的规则)在索引字符列使用 to_number。由于添加函数到索引列所以导致索引不被使用。实际上,Oracle 也只能这么做,类型转换是一个应用程序设计因素。由于转换是在每行都进行的,这会导致性能问题。详见:
Document 232243.1 ORA-01722 ORA-01847 ORA-01839 or ORA-01858 From Queries with Dependent Predicates
优化器和成本计算相关问题
-
是否存在准确且合适的统计信息(Statistics)? CBO 依赖于准确的、最新的和完整的统计信息来确定一个特定查询的最佳执行计划。如果使用 CBO,请确保统计信息已经收集。如果没有统计信息, CBO 将使用预定义的统计信息,这样是很可能不会产生良好的计划或让应用程序使用索引。请参照:
Document 754931.1 Cost Based Optimizer - Common Misconceptions and Issues - 10g and Above
其它问题
-
是否使用了视图/子查询? 查询涉及到视图或者子查询时可能会被改写,导致不使用索引(尽管该改写的目标之一是扩展更多的访问路径)。这些改写(rewrite)一般来说都是合并(merging)操作。请查看
Document 199070.1 Optimizing statements that contain views or subqueries
-
是否存在远程表(remote table)? 通常远程表不会使用索引。索引在分布式查询中的使用依赖于被发送到远程的查询。CBO 将评估远程访问的成本,并评估比较发送或者不发送索引的谓词到远程站点的成本。因此,CBO 可以做出有关远程表上使用索引的更加明智的决定。一个非常有效的方法就是,在远程建立包含相关谓词的视图并强制使用索引,之后在本地查询中使用这个视图。 请参考
Document 68809.1 Distributed Queries
-
是否使用并行执行(PX)? 在并行执行时索引的采用比在串行执行((serial execution))时更加严格。一个快速检测的方法就是禁用并行,然后查看该索引是否被使用。 -
是否是包含了子查询的Update语句? 在一些情况下,基于成本的考虑,索引没有被选使用是因为它依赖于一个子查询返回的值。这种情况下,可以使用提示(hint)来强制使用索引。请参考
Document 68084.1 Using hints to optimize an Update with a subquery that is not using an index on the updated table.
-
查询是否使用了绑定变量? CBO 对 like 或范围谓词的绑定变量不能产生准确的成本(cost)。这可能会导致索引不被选择。 请参考
Document 68992.1 Predicate Selectivity
-
查询是否引用了带有延迟约束的列? 如果一个表中的某一列上含有延迟约束(比如 NOT NULL)并且这一列上有索引,那么不管这个约束当前是延迟状态或是被显式地设置为立即使用,我们都不会考虑使用这一列上的索引。例如:
CREATE TABLE tdc ( x INT CONSTRAINT x_not_null NOT NULL DEFERRABLE INITIALLY DEFERRED RELY, y INT CONSTRAINT y_not_null NOT NULL, z VARCHAR2(30) ); CREATE INDEX t_idx ON tdc(x);
SET CONSTRAINTS ALL IMMEDIATE; <-- 将所有延迟约束置为立即使用 SET AUTOTRACE TRACEONLY EXPLAIN
SELECT COUNT(1) FROM tdc; <-- 索引不会被使用 Execution Plan ---------------------------------------------------------- Plan hash value: 2532426293 ------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | ------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | TABLE ACCESS FULL| TDC | 1 | 2 (0)| 00:00:01 | -------------------------------------------------------------------
这个现象在以下 bug 中记录,关闭为"not a bug":
Bug 17895663 optimizer ignoring deferrable constraints even when not deffered and not in tx
-
索引提示(hint)不工作 请使用表的别名。 请参考
Document 69992.1 Why is my hint ignored?
Document 50607.1 How to specify an INDEX Hint
有用的 hints:
FIRST_ROWS | 相当于提示使用索引 | ORDERED | 强制查询的关联顺序(join order of a query)。Oracle 推荐使用 LEADING hint 因为它更好用。 | LEADING | 这个 hint 告诉 optimizer 先使用指定的表做连接。它比 ORDERED 更好用。 | INDEX | 强制使用索引扫描, 并禁用快速模式(INDEX_FFS) | INDEX_FFS | 强制使用快速索引扫描INDEX_FFS | INDEX_ASC | 强制使用升序的索引范围扫描(Ascending Index Range Scan) | INDEX_DESC | 强制使用降序的索引范围扫描(Descending Index Range Scan) | 参见:
Document 29236.1 QREF: SQL Statement HINTS
|