使用大表时提高 MERGE 性能

Posted

技术标签:

【中文标题】使用大表时提高 MERGE 性能【英文标题】:Improve MERGE performance when using big tables 【发布时间】:2016-12-27 19:10:12 【问题描述】:

上下文

我们有一个模型,其中每个element 都有一个element kind 和从0 到N 的features。每个feature 只属于一个element,并且有一个feature name

这被建模为下表:

ELEMENTS
  elem_id int not null -- PK
  elem_elki_id int not null -- FK to ELEMENT_KINDS
  -- more columns with elements data

ELEMENT_KINDS
  elki_id int not null -- PK
  -- more columns with elements kinds data

FEATURES
  feat_id int not null -- PK
  feat_elem_id int not null -- FK to ELEMENTS  
  feat_fena_id int not null -- FK to FEATURE_NAMES
  -- more columns with features data

FEATURE_NAMES
  fena_id int not null -- PK
  -- more columns with feature_names data

要求

有一个用feature kinds 表替换feature names 表的新要求。 每个 (element kind, feature name) 对都有一个(而且只有一个feature kind

模型的变化是添加一个新列并创建一个新表:

ALTER TABLE features ADD feat_feki_id int null;

CREATE TABLE FEATURE_KINDS
(
  feki_id int not null, -- PK
  feki_elki_id int not null, -- FK to ELEMENT_KINDS
  feki_fena_id int null, -- FK* to FEATURE_NAMES
  -- more columns with feature kinds data
)

*feki_fena_id 实际上是一个临时列,显示哪个feature name 用于创建每个feature kind。填充feat_feki_id 后,feki_fena_id 应与feat_fena_idfeature names 表一起被丢弃。

问题

成功填充features kinds 表后,我们尝试使用以下查询填充feat_feki_id 列:

MERGE INTO features F
USING
(
    SELECT *
    FROM elements
    INNER JOIN feature_kinds
    ON elem_elki_id = feki_elki_id
) EFK
ON
(
    F.feat_elem_id = EFK.elem_id AND
    F.feat_fena_id = EFK.feki_fena_id
)
WHEN MATCHED THEN
UPDATE SET F.feat_feki_id = EFK.feki_id;

这适用于带有测试数据的小案例场景,但在生产中我们有 ~2000 万 elements~2000 feature_kinds,大约需要一个小时在抛出 ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDOTBS1' 错误之前。

问题

有什么方法可以提高MERGE 的性能以使其正常工作吗? (也许我缺少一些索引?)

是否有其他替代方法可以填充feat_feki_id 列? (我们已经尝试过UPDATE 而不是MERGE,结果相似)

【问题讨论】:

所以让段更大,或者被一些具有一组范围值的字段分解 请您在分手部分详细说明一下好吗? (扩大细分市场似乎不是一种选择......仍然需要一个多小时才能出现问题) 【参考方案1】:

目前尚不清楚是否发生了问题,或者您的撤消段是否太小。你可以在没有得到 ORA-30036 的情况下执行以下语句吗?

UPDATE features f SET f.feat_feki_id = 12345;

如果这不起作用,您只需要增加撤消段的大小。 Kludges 可以分块进行更新,但你真的不应该这样做。

假设这不是一个简单的 UNDO 大小问题,您可能会做的一件事是确保您的 MERGE(或 UPDATE)按照它们在表中出现的顺序更新行。否则,您可能会一遍又一遍地重新访问相同的块,这确实会损害性能并增加 UNDO 的使用率。我在几年前不得不做的类似手术中遇到了这个问题,当我终于弄明白时我感到很震惊。

为了避免我遇到的问题,你需要这样的东西:

MERGE INTO features F
USING
(
    SELECT f.feat_id, fk.feki_id
    FROM features f
    INNER JOIN elements e ON e.elem_id = f.feat_elem_id
    INNER JOIN feature_kinds fk ON fk.feki_elki_id = e.elem_elki_id and fk.feki_fena_id = f.feat_fena_id
    -- Order by the ROWID of the table you are updating to ensure you are not revisiting the same block over and over
    ORDER BY f.rowid
) EFK
ON
(
    F.feat_id = efk.feat_id )
)
WHEN MATCHED THEN
UPDATE SET F.feat_feki_id = EFK.feki_id;

我可能弄错了您的数据模型,但关键是在MERGE 查询和ORDER BY features.rowid 中包含FEATURES 表,以确保更新按行顺序进行。

【讨论】:

谢谢,我会尽可能测试一下。即使我遇到了 UNDO 表空间问题,耗时一个多小时的查询似乎也有问题。目前,我担心features 表几乎与elements 表一样大,因此您建议的双重连接可能会导致一个巨大的表。你认为这会是个问题吗? 是的,这可能是个问题。连接两个 20M 行表(加上第三个较小的表)并对结果进行排序是很费力的。如果可以的话,使用并行查询来提供帮助。查询计划应该涉及大量的全扫描和散列连接,因此您应该能够在运行时查询V$SESSION_LONGOPS 以查看查询的进展情况。 此解决方案有效,但ORDER BY 导致TEMP 表空间的大量使用。就我而言,大约 50 GB。为了让它工作,我必须为该表空间创建一个附加文件,正如这个答案所示:***.com/a/25350967 感谢您的反馈!我想知道是否有办法编写查询,使其自然按features.rowid 排序,如果是这样,依赖这种行为是否安全。我在想可能是一个提示,使FEATURES 成为散列连接中的所谓“探针表”而不是“构建表”。如果您需要更快地获得它和/或摆脱 50GB 临时使用量,可能值得进一步调查。 至于性能,整个查询在不到一个小时的时间内运行,并且不应该如此频繁地运行,因此您当前的解决方案可以解决问题。我会考虑您关于避免订购的提示的其他建议。

以上是关于使用大表时提高 MERGE 性能的主要内容,如果未能解决你的问题,请参考以下文章

如何提高包含 Update 的 Merge 语句的性能

加入两个大表时的性能问题

使用视图提高查询性能

如何提高 BigQuery 读取性能

通过 CLUSTER 提高 SparkSQL 查询性能

加入大表时,postgres 查询速度慢