使用Rownum和order by子句的查询不使用索引

时间:2014-10-30 20:48:27

标签: sql oracle

我正在使用Oracle(企业版10g),我有这样的查询:

SELECT * FROM (
SELECT * FROM MyTable
  ORDER BY MyColumn
) WHERE rownum <= 10;

MyColumn已被编入索引,但是,Oracle出于某种原因在切割前10行之前进行全表扫描。因此,对于包含400万条记录的表格,上述时间约为15秒。

现在考虑这个等效的查询:

SELECT MyTable.*
FROM
  (SELECT rid
  FROM
    (SELECT rowid as rid
    FROM MyTable
    ORDER BY MyColumn
    )
  WHERE rownum <= 10
  ) 
INNER JOIN MyTable
ON MyTable.rowid = rid
ORDER BY MyColumn;

这里Oracle扫描索引并找到前10个rowid,然后使用嵌套循环按rowid查找10条记录。对于400万张表,这只需不到一秒钟。

  • 我的第一个问题是为什么优化器会对上面的第一个查询做出如此糟糕的决定?
  • 我的第二个也是最重要的问题是:是否有可能使第一个查询表现更好。我特别需要使用第一个查询尽可能未经修改。我正在寻找比上面的第二个查询更简单的东西。谢谢!

请注意,由于特殊原因,我无法使用/*+ FIRST_ROWS(n) */提示或ROW_NUMBER() OVER (ORDER BY column)构造。

1 个答案:

答案 0 :(得分:1)

如果在您的情况下这是可接受的,添加WHERE ... IS NOT NULL子句将有助于优化器使用索引,而不是在使用ORDER BY子句时执行全表扫描:

SELECT * FROM (
SELECT * FROM MyTable
WHERE MyColumn IS NOT NULL
--    ^^^^^^^^^^^^^^^^^^^^
ORDER BY MyColumn
) WHERE rownum <= 10;

理性是Oracle不会在索引中存储NULL值。在您最初编写查询时,优化器决定进行全表扫描,就好像少于10个非NULL值一样,它应该检索一些&#34; NULL行&#34;到&#34;填写&#34;剩下的行。显然,如果索引包含足够的行,则首先检查它是不够聪明的......

添加WHERE MyColumn IS NOT NULL后,您会通知优化工具,在任何情况下,您都不希望NULL中有MyColumn行。因此,它可以盲目地使用索引,而不必担心NULLMyColumn的假设行。


出于同样的原因,将ORDER BY列声明为NOT NULL会阻止优化程序执行全表扫描。因此,如果您可以更改架构,则更清晰的选项是:

ALTER TABLE MyTable MODIFY (MyColumn NOT NULL);

请参阅http://sqlfiddle.com/#!4/e3616/1进行各种比较(点击查看执行计划