【问题标题】:How to improve a SCN-based query performance?如何提高基于 SCN 的查询性能?
【发布时间】:2013-05-16 13:28:10
【问题描述】:

在 Oracle 数据库中有一个pseudocolumn which's called ora_rowscn。如果检索到它,它将显示该行最近更改的 SCN(如文档中所述)。

还有一个选项 rowdependenciesCREATE TABLE 可以为每一行而不是整个数据块(默认)打开 SCN 的存储。

因此,我使用此列的值来指示哪些行已更新并且需要上传到另一个数据库。

让我们考虑这个例子:

  1. 架构S1 中有一个表T1,其中包含数百万条记录(常规查询无法承受对表的全面扫描)。

    CREATE TABLE T1 {
      A INTEGER PRIMARY KEY,
      B VARCHAR2(100),
      C DATE
    }
    /
    
  2. S2, S3, S4, S5.. 的架构,每个架构中都有T2 的表。

    CREATE TABLE T2 {
      A INTEGER
    }
    /
    
  3. T2 中只有一行,但 T2.A 的值在不同的架构中可能不同。

所以,我需要在每个模式 (S2, S3, S4...) 中检索来自 S1.T1 的所有行,其中 ora_rowscn 的值大于 S*.T2.A(然后我使用这个数据块)。 获取这些行后,我用当前系统 SCN (dbms_flashback.get_system_change_number) 重写 S*.T2.A 的值。

任何架构的以下查询都在这里:

查询 1:

SELECT * FROM S1.T1 WHERE ora_rowscn > (SELECT A FROM T2);

查询 2(它在我完成上一个查询返回的数据集的工作时执行):

UPDATE T2 SET A = dbms_flashback.get_system_change_number;

问题是查询 1 的性能不可接受(对表 S1.T1 进行全面扫描)并且无法为列 ora_rowscn 建立索引。

问题:有哪些方法可以提高查询 1 的性能?

【问题讨论】:

  • 查询 1 和 2 假设每分钟执行一次。
  • 为什么不直接使用闪回查询表select * from S1.T1 as of timestamp (sysdate - 1)??
  • 也许是因为它完全不同?我不需要过去的数据,我需要自上次查询以来的所有更新数据
  • 希望您的真实版本考虑到在您开始查询 1 和从查询 2 获取当前 SCN 之间的 SCN 增量?否则,每次运行的数据都会出现漏洞,无论你跑得有多快。 (使用 SCN 或 last_updated 字段)。我想解决这个问题的明显方法是在T2 中存储两个值并在它们之间进行查询。
  • 嗯,是的,你是对的。这就是为什么我在执行查询1之前实际上将当前SCN的值保存在一个变量中,完成后我将保存的值写入T2,这里为了简化问题省略了这个事实

标签: oracle oracle10g


【解决方案1】:

您不能索引ora_rowscn。因此,查询 1 的最佳计划是FULL TABLE SCAN

由于这是不可接受的,您将不得不使用另一个标记,例如 last_updated date 列。此列是可索引的,但您必须对其进行更新。您可以使用小型轻量级触发器自动执行此更新。

查询 1 针对索引列的性能将取决于检索到的行数。

【讨论】:

  • 我已经考虑过所谓的“用户定义的 SCN”,当您使用用户定义的 sequenceinteger 列时,该列包含 sequence 的下一个值,每次行已更新,但我遇到了一些与数据集中漏洞的冲突(一些同时更新的行丢失了,现在不记得为什么了).. 使用datetimestamp 的选项是一个不错的主意,值得一试试过了。不能说它现在是否总是有效,但会希望。
  • 还有其他想法吗?我的眼睛被引导到它们的物化视图和索引上,但不确定它是否会起作用。谈论这个解决方案 - 我在这里遇到了一些与锁有关的严重问题 - 由于last_updated 列经常更改,我需要更新关于它的索引的统计信息,可能会重建索引本身,但这几乎不可能,因为频繁更新会阻止重建索引(资源很忙)。
  • @ZZa物化视图日志会记录对源表的所有更改,它基本上是一个表本身,所以我想你可以索引和查询它。但是,将其用于手动处理很棘手且不受支持/未记录。
  • @zza 这里的另一个想法是设置 AQ。您有 N 个数据消费者,自上次清空队列以来,每个消费者都会收到更新/插入。
  • @zza 我不明白这里关于锁的问题:需要更新/删除的 N 模式不更新主表,它们只是 readers 和因此不能阻塞其他进程,也不能被阻塞。
【解决方案2】:

您只关心自上次运行上传以来插入或更新了哪些行。

为什么不在表中添加一个标志,将每一行标记为“脏” - 即插入前触发器会将其设置为“X”,而更新前触发器会将其设置为“X”。

然后,编写您的上传过程以查询表中标志为 NOT NULL 的任何行,然后在成功时将标志设置为 NULL。请注意,这意味着在上传过程中插入/更新的新行仍将被正确标记,并在下次上传运行时被拾取。

如果插入/更新的行数与表中的总行数相比是一个非常小的比例,那么在标志上的索引将很有用,因为 NULL 标志不会存储在索引中(即索引通常会变小。

编辑:

对于需要更新多个架构的情况,一个等效的解决方案是使用一个单独的表来保存已更改行的 ID。该表应该有每个目标模式的标志。当所有标志都设置为 NULL 后,从表中删除该行。

【讨论】:

  • 这种方式一般来说是不正确的,它只有在你有一个需要知道更改的模式时才有效,因为在使用数据集后你重置了 dirty 标志和它不再可用于其他模式。然后,我需要与我拥有的模式一样多的标记。但这是不可接受的,它们是动态添加的,现在大约有三百列,想象一下在一个有几百万行的表上添加三百列及其索引意味着什么(至少在大小方面)。
  • 很好 - 但仅仅因为您认为这对您的情况不切实际并不意味着它不是“正确的”。有效,我试过了。
  • 如果您选择了一个带有非空标志的行,那么它会被更改,如果您随后将标志设置回空,您不会丢失该更改吗?似乎除非您能保证在选择行和重置标志之间没有变化,否则数据更改将丢失?
  • 好点大卫 - 我们需要防止破坏并发更改,例如还添加一个 VERSION_ID 列,每次更新都会增加该列;只要 VERSION_ID 与查询时相同,我们的上传过程只会将标志更新为 NULL,例如UPDATE mytable SET flag = NULL WHERE version_id = :version_id_as_queried.
猜你喜欢
  • 1970-01-01
  • 2016-01-16
  • 1970-01-01
  • 1970-01-01
  • 2013-08-05
  • 1970-01-01
  • 2016-01-17
  • 1970-01-01
  • 2013-03-21
相关资源
最近更新 更多