Oracle中分页查询的速度

时间:2011-05-17 15:18:27

标签: sql performance oracle11g rownum window-functions

这对我来说是一个永无止境的话题,我想知道我是否会忽视某些事情。基本上我在应用程序中使用两种类型的SQL语句:

  1. 具有“后备”限制的常规查询
  2. 已排序和已分页的查询
  3. 现在,我们正在谈论针对具有数百万条记录的表的一些查询,加入另外5个具有数百万条记录的表。显然,我们几乎不想获取所有这些,这就是为什么我们有上述两种方法来限制用户查询。

    案例1 非常简单。我们只需添加一个ROWNUM过滤器:

    WHERE ...
      AND ROWNUM < ?
    

    这是非常快的,因为Oracle的CBO将考虑其执行计划考虑此过滤器,并可能应用FIRST_ROWS操作(类似于/*+FIRST_ROWS*/提示强制执行的操作。

    然而,

    案例2 对Oracle来说有点棘手,因为在其他RDBMS中没有LIMIT ... OFFSET子句。因此,我们将“业务”查询嵌套在技术包装器中:

    SELECT outer.* FROM (
      SELECT * FROM (
        SELECT inner.*, ROWNUM as RNUM, MAX(ROWNUM) OVER(PARTITION BY 1) as TOTAL_ROWS
        FROM (
          [... USER SORTED business query ...]
        ) inner
      ) 
      WHERE ROWNUM < ?
    ) outer
    WHERE outer.RNUM > ?
    

    请注意,计算TOTAL_ROWS字段是为了知道即使不提取所有数据也会有多少页。现在这个分页查询通常非常令人满意。但是时不时(正如我所说,在查询5M +记录时,可能包括非索引搜索),这会运行2-3分钟。

    编辑:请注意,潜在的瓶颈并不容易规避,因为在分页之前必须应用排序!

    我想知道,这是LIMIT ... OFFSET最先进的模拟,包括Oracle中的TOTAL_ROWS,还是有更好的解决方案,设计更快,例如使用ROW_NUMBER()窗口函数而不是ROWNUM伪列?

4 个答案:

答案 0 :(得分:6)

案例2的主要问题是,在许多情况下,必须获取整个查询结果集,然后在之前对进行排序可以返回前N行 - 除非对ORDER BY列进行索引并且Oracle可以使用索引来避免排序。对于复杂查询和大量数据,这可能需要一些时间。但是,您可以采取一些措施来提高速度:

  1. 尝试确保在内部SQL中不调用任何函数 - 这些函数可能会被调用500万次,只返回前20行。如果您可以将这些函数调用移动到外部查询,则会调用它们。
  2. 使用FIRST_ROWS_n提示推动Oracle优化,因为您永远不会返回所有数据。
  3. 修改

    另一个想法是:您当前正在向用户展示可能返回数千或数百万行的报告,但用户实际上从未真实地翻阅所有行。你能不能强迫他们选择较少量的数据,例如通过将所选日期范围限制为3个月(或其他)?

答案 1 :(得分:3)

您可能希望跟踪需要花费大量时间的查询并查看其解释计划。最有可能的是性能瓶颈来自TOTAL_ROWS计算。 Oracle必须读取所有数据,即使您只获取一行,这是所有RDBMS都面临此类查询的常见问题。没有TOTAL_ROWS的实现可以解决这个问题。

加速此类查询的根本方法是放弃TOTAL_ROWS计算。只显示有其他页面。您的用户是否真的需要知道他们可以翻阅52486页?估计可能就足够了。这是另一个解决方案,例如通过谷歌搜索实现:估算页数而不是实际计算它们。

设计准确有效的估算算法可能并非易事。

答案 2 :(得分:3)

“LIMIT ... OFFSET”几乎是语法糖。它可能会使查询看起来更漂亮,但是如果您仍然需要读取整个数据集并对其进行排序并获得“50-60”行,那么这就是必须完成的工作。

如果您有正确顺序的索引,那么这可以提供帮助。

答案 3 :(得分:1)

运行两个查询而不是尝试count()并在同一查询中返回结果可能会更好。 Oracle可能能够回答count()而无需任何排序或连接到所有表(基于声明的外键约束连接表消除)。这是我们在申请中通常所做的事情。对于性能重要的语句,我们编写一个单独的查询,我们知道它将返回正确的计数,因为我们有时可以做得比Oracle好。

或者,您可以在数据的性能和新近度之间进行权衡。带回前5页几乎与恢复第一页一样快。因此,您可以考虑将5页的结果存储在临时表中,并附上信息的到期日期。如果有效,请从临时表中获取结果。放入后台任务以定期删除过期数据。