【问题标题】:What is the best way to ensure consistent ordering in an Oracle query?在 Oracle 查询中确保一致排序的最佳方法是什么?
【发布时间】:2015-04-09 15:48:58
【问题描述】:

我有一个程序需要对许多非常大的 Oracle 表(最大的有数千万行)运行查询。这些查询的输出被馈送到另一个进程(作为副作用)可以记录查询的进度(即,最后一行获取)。

如果任务由于某种原因中途停止,可以重新启动它会很好。为此,查询必须以一致的顺序返回行,因此必须对其进行排序。显而易见的事情是对主键进行排序;但是,与未排序的解决方案相比,这可能会在性能(索引访问)方面受到惩罚。鉴于重启可能永远不会发生,这是不可取的。

是否有一些技巧可以以另一种方式确保一致的排序?在这种情况下保持性能的任何其他建议?

编辑:我一直在环顾四周,看到提到“按 rowid 排序”。这有用甚至可能吗?

EDIT2:我正在添加一些基准:

  • 没有订单:17 秒。
  • 按 PK 排序:46 秒。
  • 按 rowid 排序:43 秒。

因此,任何 order by 都会对性能产生严重影响,使用 rowid 几乎没有什么区别。公认的答案是 - 没有简单的方法。

【问题讨论】:

  • 您认为为什么会使用索引访问?如果要检索大部分数据,则索引将毫无意义。行以一致的顺序出现(例如主键升序)的唯一保证是使用ORDER BY 子句。但是,您还必须考虑诸如如果有人在查询开始和重新启动之间插入出现在有序结果集中中途的新数据怎么办?那可能会把事情搞砸。不过,对结果集进行排序很可能会对性能产生影响。
  • 对于这种情况,您可以假设基础表不会被修改。如果使用 order by,我假设需要索引访问才能按顺序检索行。
  • 索引不一定用于进行排序。我说的不是基础表的变化,而是说你有一个 pk id 为 1、3、5 和 7 的行,所以你在 id 列上对结果进行排序。假设您在第 3 行之后停止,然后有人插入了 id = 4 的行。当您重新启动查询时,id = 5 的行不再是结果集中的第 3 行,而是第 4 行,因此您的结果不是在两次运行中保持一致。
  • @Boneist,您如何解释“基础表正在更改”,使其不包括“有人插入一行”?
  • 分区是一种选择吗?如果表是散列分区的,您可以一次处理一个分区(检索特定分区不会产生任何成本),然后从最新的分区重新启动。您实际上不需要对数据进行排序,只需将它们放入(确定性)哈希桶中就足够了。

标签: sql oracle sorting sql-order-by data-consistency


【解决方案1】:

我能想到的最佳建议是减少发生可能导致进程停止的问题的可能性,这意味着保持代码简单。没有游标,没有提交,没有尝试移动部分数据,只是直接的 SQL 语句。

除非完全重启将是一场完全不可接受的灾难,否则我会为了简单起见而根本不使用任何中途重启代码。

【讨论】:

  • 投票赞成基本上说“你不能”,这总是一个勇敢的答案!如果找不到替代品,我会接受。
  • 哦,当然总有替代方案。不过,这绝对是一个权衡。我曾经遇到过绝对需要“可恢复”作业的情况,因为它必须每 24 小时运行一次,并且需要 18 小时才能完成。请注意,如果它更简单且不可恢复,它可能只需要 12 次。在我遇到的 95% 的案例中,最好的防御是良好的攻击,这意味着代码小、快、不复杂。跨度>
【解决方案2】:

如果您想要一些订单并且查询的数据是未排序的,那么您无论如何都需要对其进行排序,并花费一些资源进行排序。
所以,至少有两种优化方式:

  1. 尽量减少用于分类的资源;
  2. 查询已排序的数据。

对于第一个变体,Oracle 会自行计算最佳变体,以最大限度地减少数据访问和总体查询时间。可能可以选择优化器已经使用的唯一索引中涉及的排序顺序,但这是一个非常值得怀疑的策略。

第二个变体是关于索引组织的表和关于强制 Oracle 使用某些特定索引的提示。如果您需要处理某个特定表中的几乎所有记录,这似乎没问题,但是如果查询的选择性很高,那么即使在单个表上,它也会显着减慢进程。

考虑一个具有代理主键的表,其中包含具有 10 年交易历史的数据。如果您只需要前一年的数据并且您强制按主键排序,那么 Oracle 需要一个一个地处理所有 10 年的记录,以查找属于单个年份的所有记录。
但是,如果您需要该表中 9 年的数据,那么全表扫描可能比基于索引的选择更快。
因此,查询的选择性是在全表扫描和结果排序之间进行选择的关键。

对于存储结果和重新启动查询,一个好的解决方案是使用 Oracle Streams Advanced Queuing 来提供另一个进程。
队列中所有未处理的消息都重定向到异常队列,可以单独处理。
因为您没有为选定的消息指定确切的顺序,我想您只需要排序来维护记录的未处理部分。如果这是真的,那么使用 AQ,您根本不需要订购,甚至可以并行处理记录。

所以,最后,从我的角度来看,Buffered Queue 是你真正需要的。

【讨论】:

  • 只是为了澄清开始“想想......”的观点:事实上,我可能需要超过 95% 的表格,所以对于无序选择,FTS 就是它会做的据我所知,没有理由查看主索引。但是,如果我在 PK 上添加订单,则必须查看该索引。因此,实际上,相对而言,对 PK 进行更严格的选择会更好,因为无论如何它都必须访问 WHERE 的索引,因此使用它进行排序不会丢失任何内容。但在我的情况下,通常不需要索引,因此排序是纯粹的开销。
  • 请注意,我提到了index-organized tables,如果查询结果仅按主键排序,它不会增加开销。但是重组一个大表本身就很麻烦,所以它可能不适用于你的情况。
  • 是的 - 在我的情况下不是一个可用的选项。无论如何,谢谢。
【解决方案3】:

您可以跳过排序,只需使用SET is_processed = 'Y'SET date_processed = sysdate 之类的内容更新您处理的记录。完全可重启且无排序。

为了提高性能,您可以按 is_processed 进行分区。是的,分区键更改可能会很慢,但这都是权衡取舍。

【讨论】:

  • 我认为更新会比按主键排序要慢。另外,我必须对标志做一个 where 条件。优化器仍然需要全表扫描,因为这些列上没有索引,如果有,则必须对其进行更新,这会使其速度更慢,而且它可能没有足够高的基数来产生影响.
  • 编辑答案以解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-09
  • 1970-01-01
  • 2014-07-13
  • 2013-03-11
  • 2010-10-14
相关资源
最近更新 更多