我有一个程序,需要对许多非常大的Oracle表(最大的表有数千万行)运行查询。这些查询的输出被馈送到另一个进程中,该进程(作为副作用)可以记录查询的进度(即,提取的最后一行)。
如果任务由于某种原因中途停止,可以重新启动,那就太好了。要做到这一点,查询必须以一致的顺序返回行,因此必须对其进行排序。显而易见的做法是对主键进行排序;然而,与非排序解决方案相比,这可能会在性能(索引访问)方面受到惩罚。考虑到重启可能永远不会发生,这是不可取的。
是否有一些技巧可以通过另一种方式确保一致的排序?在这种情况下,还有其他保持性能的建议吗?
编辑:我一直在环顾四周,看到有人提到"rowid订购"。这有用吗,甚至可能吗?
编辑2:我正在添加一些基准:
- 无订单截止时间:17秒
- 按PK排序:46秒
- 按rowid排序:43秒
因此,任何order by都会对性能产生严重影响,而使用rowid几乎没有什么区别。公认的答案是-没有简单的方法。
我能想到的最好的建议是减少可能停止进程的问题发生的可能性,这意味着保持代码的简单性。没有游标,没有提交,没有试图移动部分数据,只有直接的SQL语句。
除非完全重新启动是一场完全不可接受的灾难,否则我会追求简单,根本不需要任何中途重新启动代码。
如果您想要一些订单,并且查询的数据未排序,那么您无论如何都需要对其进行排序,并花费一些资源进行排序
因此,至少有两种优化变体:
- 最大限度地减少用于排序的资源
- 查询已排序的数据
对于第一个变体,Oracle自己计算一个最佳变体,以最大限度地减少数据访问和总体查询时间。可以选择优化器已经使用的唯一索引中涉及的排序顺序,但这是一种非常值得怀疑的策略。
第二种变体是关于索引组织的表,以及通过提示强制Oracle使用某些特定的索引。如果您需要处理某个特定表中几乎所有的记录,这似乎是可以的,但如果查询的选择性很高,则会显著降低处理速度,即使是在单个表上。
考虑一个具有代理主键的表,该表保存有10年交易历史的数据。如果您只需要上一年的数据,并且按主键强制订购,那么Oracle需要逐一处理所有10年的记录,以查找属于某一年的所有记录
但是,如果您需要该表中9年的数据,那么全表扫描可能比基于索引的选择更快
所以查询的选择性是在全表扫描和结果排序之间进行选择的关键。
对于存储结果和重新启动查询,一个好的解决方案是使用Oracle Streams Advanced Queuing为另一个进程提供消息
队列中所有未处理的消息都重定向到异常队列,在那里可以单独处理
因为您没有为所选消息指定确切的排序,所以我想您只需要排序来维护未处理的部分记录。如果这是真的,那么使用AQ,您根本不需要订购,甚至可以并行处理记录。
最后,从我的角度来看,缓冲队列才是您真正需要的。
您可以跳过排序,只更新用SET is_processed = 'Y'
或SET date_processed = sysdate
之类的东西处理的记录。完全可重启,无需订购。
为了提高性能,您可以通过is_processed进行分区。是的,分区键的更改可能很慢,但这都是关于权衡的。