【发布时间】:2009-04-06 13:58:17
【问题描述】:
我们有 Oracle 10g,我们需要查询 1 个表(无连接)并过滤掉其中 1 个列为空的行。当我们这样做时——WHERE OurColumn IS NOT NULL——我们在一个非常大的表上进行全表扫描——坏坏坏。该列上有一个索引,但在这种情况下它会被忽略。有什么解决办法吗?
谢谢
【问题讨论】:
标签: oracle optimization null
我们有 Oracle 10g,我们需要查询 1 个表(无连接)并过滤掉其中 1 个列为空的行。当我们这样做时——WHERE OurColumn IS NOT NULL——我们在一个非常大的表上进行全表扫描——坏坏坏。该列上有一个索引,但在这种情况下它会被忽略。有什么解决办法吗?
谢谢
【问题讨论】:
标签: oracle optimization null
优化器认为全表扫描会更好。
如果只有几行NULL,则优化器是正确的。
如果您绝对确定索引访问会更快(也就是说,您有超过75% 行和col1 IS NULL),那么提示您的查询:
SELECT /*+ INDEX (t index_name_on_col1) */
*
FROM mytable t
WHERE col1 IS NOT NULL
为什么是75%?
因为使用INDEX SCAN 来检索索引未涵盖的值意味着在ROWID 上进行隐藏连接,其成本约为4 的表扫描的倍数。
如果索引范围包含的行数超过25%,则表扫描通常会更快。
正如Tony Andrews 所提到的,聚类因子是衡量这个值的更准确的方法,但25% 仍然是一个很好的经验法则。
【讨论】:
IS NOT NULL 在可空列上使该列上的索引有资格包含在计划中,Oracle 对此足够聪明:)
优化器将根据全表扫描的相对成本和使用索引来做出决定。这主要归结为必须读取多少块才能满足查询。另一个答案中提到的 25%/75% 的经验法则很简单:在某些情况下,即使获得 1% 的行,全表扫描也是有意义的——即,如果这些行恰好分布在许多块中。
例如,考虑这个表:
SQL> create table t1 as select object_id, object_name from all_objects;
Table created.
SQL> alter table t1 modify object_id null;
Table altered.
SQL> update t1 set object_id = null
2 where mod(object_id,100) != 0
3 /
84558 rows updated.
SQL> analyze table t1 compute statistics;
Table analyzed.
SQL> select count(*) from t1 where object_id is not null;
COUNT(*)
----------
861
如您所见,T1 中只有大约 1% 的行具有非空 object_id。但是由于我构建表格的方式,这 861 行将或多或少地均匀分布在表格周围。因此,查询:
select * from t1 where object_id is not null;
可能会访问 T1 中的几乎每个块来获取数据,即使优化器使用了索引。那么放弃索引并进行全表扫描是有意义的!
帮助识别这种情况的一个关键统计数据是索引聚集因子:
SQL> select clustering_factor from user_indexes where index_name='T1_IDX';
CLUSTERING_FACTOR
-----------------
460
这个值 460 相当高(与索引中的 861 行相比),表明将使用全表扫描。见this DBAZine article on clustering factors。
【讨论】:
如果您正在执行选择 *,那么执行表扫描而不是使用索引是有意义的。如果您知道您对哪些列感兴趣,您可以使用这些列加上您应用 IS NOT NULL 条件的列创建一个覆盖索引。
【讨论】:
这取决于您在表上的索引类型。
大多数 B 树索引不存储空条目。位图索引确实存储空条目。
所以,如果你有:
从我的表中选择 * 其中 mycolumn 为空
并且您在mycolumn 上有一个标准的 B 树索引,那么查询不能使用该索引,因为“null”不在索引中。
(如果索引针对多个列,并且索引列之一不为空,则索引中将有一个条目。)
【讨论】:
is not null 查找,而不是is null。
在该列上创建索引。
为了确保索引被使用,它应该在索引和其他列的where中。
ocdecio 回答:
如果您正在执行选择 *,那么执行表扫描而不是使用索引是有意义的。
这并不完全正确;如果存在适合您的 where 子句的索引,则将使用索引,并且查询优化器决定使用该索引比执行表扫描更快。如果没有索引,或者没有合适的索引,则必须进行表扫描。
【讨论】:
还值得检查一下 Oracle 对表的统计信息是否是最新的。它可能不知道全表扫描会更慢。
【讨论】:
Oracle 数据库根本不会在常规(b-tree)索引中索引空值,因此它不能使用它,也不能强制 oracle 数据库使用它。
BR
【讨论】:
is NOT null的。所有相关值都将在索引中。
使用提示只能作为一种变通方法,而不是一种解决方案。
正如其他答案中提到的,空值在 B-TREE 索引中不可用。
既然您知道在此列中大部分为空值,您是否可以将空值替换为例如范围。
这实际上取决于您的列和数据的性质,但通常情况下,如果您的列是日期类型,例如:
where mydatecolumn is not null
可以翻译成一条规则:我想要所有有日期的行。
那么您绝对可以这样做: 其中 mydatecolumn
这将返回所有带有日期的行并省略空值,同时利用该列上的索引而不使用任何提示。
【讨论】:
见http://www.oracloid.com/2006/05/using-index-for-is-null/
如果您的索引位于单个字段上,则不会使用它。尝试在索引中添加虚拟字段或常量:
create index tind on t(field_to_index, 1);
【讨论】: