我们有 Oracle 10g,我们需要查询一个表(没有
JOIN
)并过滤掉其中 1 列为空的行。当我们这样做时 - WHERE OurColumn IS NOT 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%
仍然是一个很好的经验法则。
优化器将根据全表扫描和使用索引的相对成本做出决定。这主要取决于必须读取多少块才能满足查询。另一个答案中提到的 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 行相比),表明将使用全表扫描。请参阅 这篇关于聚类因素的 DBAZine 文章。
如果您正在进行 select *,那么进行表扫描而不是使用索引是有意义的。如果您知道对哪些列感兴趣,则可以使用这些列加上您要应用 IS NOT NULL 条件的列来创建覆盖索引。
这可能取决于表上索引的类型。
大多数 B 树索引不存储空条目。位图索引 do 存储空条目。
所以,如果你有:
从我的表中选择* 其中 mycolumn 为空
并且您在
mycolumn
上有一个标准 B 树索引,那么查询 can't 使用该索引,因为“null”不在索引中。
(如果索引针对多个列,并且索引列之一不为空,则索引中将会有一个条目。)
在该列上创建索引。
为了确保索引被使用,它应该位于索引和where中的其他列上。
ocdecio 回答:
如果您正在执行 select *,那么进行表扫描而不是使用索引会更有意义。
这并不完全正确;如果存在适合您的 where 子句的索引,并且查询优化器决定使用该索引比执行表扫描更快,则将使用索引。如果没有索引,或者没有合适的索引,才必须进行表扫描。
还值得检查 Oracle 表上的统计信息是否是最新的。它可能不知道全表扫描会更慢。
Oracle 数据库根本不在常规(b 树)索引中索引空值,因此它不能使用它,您也不能强制 Oracle 数据库使用它。
BR
使用提示只能作为解决方法,而不是解决方案。
正如其他答案中提到的,空值在 B-TREE 索引中不可用。
既然您知道此列中大部分为空值,那么您是否能够用一个范围来替换空值。
这实际上取决于您的列和数据的性质,但通常情况下,例如,如果您的列是日期类型:
where mydatecolumn is not null
可以翻译成一条规则:我想要所有有日期的行。
那么你绝对可以这样做: 我的日期列在哪里 <=sysdate (in oracle)
这将返回带有日期的所有行并忽略空值,同时利用该列上的索引而不使用任何提示。
参见 http://www.oracloid.com/2006/05/using-index-for-is-null/
如果您的索引位于一个字段上,则不会使用该索引。尝试在索引中添加虚拟字段或常量:
create index tind on t(field_to_index, 1);