10

这对我来说是一个永无止境的话题,我想知道我是否可能忽略了一些东西。本质上,我在应用程序中使用两种类型的 SQL 语句:

  1. 具有“回退”限制的常规查询
  2. 排序和分页查询

现在,我们正在讨论对具有数百万条记录的表的一些查询,并与另外 5 个具有数百万条记录的表相连接。显然,我们几乎不想获取所有这些,这就是为什么我们有上述两种方法来限制用户查询。

案例1非常简单。我们只是添加了一个额外的ROWNUM过滤器:

WHERE ...
  AND ROWNUM < ?

这是相当快的,因为 Oracle 的 CBO 会在其执行计划中考虑这个过滤器,并且可能会应用一个FIRST_ROWS操作(类似于/*+FIRST_ROWS*/提示强制执行的操作。

但是,情况 2对 Oracle 来说有点棘手,因为没有LIMIT ... OFFSET其他 RDBMS 中的子句。因此,我们将“业务”查询嵌套在技术包装器中,如下所示:

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,包括TOTAL_ROWS在 Oracle 中,还是有更好的解决方案,在设计上会更快,例如通过使用ROW_NUMBER()窗口函数而不是ROWNUM伪列?

4

4 回答 4

6

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

  1. 尽量确保在内部 SQL 中没有调用任何函数——这些函数可能会被调用 500 万次,只是为了返回前 20 行。如果您可以将这些函数调用移动到外部查询,它们将被调用更少。
  2. 使用 FIRST_ROWS_n 提示推动 Oracle 针对您永远不会返回所有数据这一事实进行优化。

编辑:

另一个想法:您当前正在向用户呈现一个可能返回数千或数百万行的报告,但用户永远不会真正地翻阅它们。你能不能强迫他们选择更少量的数据,例如通过将选择的日期范围限制为 3 个月(或其他)?

于 2011-05-17T15:44:16.437 回答
3

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

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

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

于 2011-05-17T15:38:30.387 回答
3

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

如果您有正确顺序的索引,那么这会有所帮助。

于 2011-05-18T00:26:35.867 回答
1

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

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

于 2011-05-18T08:59:03.837 回答