如何使用多列索引优化 MERGE

Posted

技术标签:

【中文标题】如何使用多列索引优化 MERGE【英文标题】:How to optimize MERGE using multiple column index 【发布时间】:2015-05-07 21:23:55 【问题描述】:

我是 MERGE 的新手和索引新手,所以请耐心等待...

我有一个存储过程,它构建一个 #changes 临时表,根据 #changes 更新一个 prod_tbl 表,然后将之前和之后的值插入到 auto_update_log 表中,该表随着重复行快速增长。为了防止这种情况,我想使用 MERGE 语句。速度是最重要的,线程安全也是最重要的,因为这个表会被操作一整天。

auto_update_log 上没有任何现有索引,也没有任何类型的键。我的想法是使用键列(来自auto_update_log 表)和所有前后列创建一个多列索引,以帮助加快 MERGE 的速度。将有 6 个前后列加上与auto_update_log 的 PK 相关的一键列。

示例日志表:

CREATE TABLE dbo.sample_auto_update_log (
    id INT NOT NULL, --Primary key from [prod_tbl]
    item_a_before VARCHAR(25) NULL, --[prod_tbl].[item_a]
    item_a_detail VARCHAR(25) NULL, --Value from elsewhere in the DB that applies
    item_a_after VARCHAR(25) NULL, --The new value SET for [prod_tbl].[item_a]
    update_count INT NOT NULL DEFAULT (0),
    update_datetime DATETIME NOT NULL DEFAULT (GETDATE())
);

示例合并:

MERGE sample_auto_update_log WITH (HOLDLOCK) AS t
USING #changes AS s
ON (t.id = s.id AND t.item_a_before = s.item_a_before AND t.item_a_after = s.item_a_after)
WHEN MATCHED THEN
    UPDATE
    SET update_count = update_count + 1, update_datetime = GETDATE()
WHEN NOT MATCHED THEN
    INSERT (id, item_a_before, item_a_detail, item_a_after)
    VALUES (s.id, s.item_a_before, s.item_a_detail, s.item_a_after);

SQL Fiidle

问题: 如何使用索引或其他方法最好地优化 MERGE?

【问题讨论】:

老实说,您最好不要使用 MERGE 进行 upserts。 MERGE 有许多错误,像这样的 upsert 就是其中之一。 mssqltips.com/sqlservertip/3074/… @SeanLange ,你有什么好的选择吗?我已经阅读了几篇文章,包括您发布的文章,似乎很多这些问题不适用于我的情况。例如,我没有在我的目标或源表中使用主键,没有变量或表变量,没有在触发器中使用它,我已经在使用 (HOLDLOCK) 提示来防止竞争条件等。我也可能有夸大了线程安全的重要性——性能是一个更大的问题,因为用户不太可能同时访问相同的记录,但这会运行很多次。 为什么不直接发出两条语句加入您的临时表?第一个是更新,然后是插入。 真的只是因为我阅读MERGE可以更快。我想使用这里建议的方法:***.com/a/21209131/550595,但这似乎只适用于单行。不过,我可能只使用两条语句,如果这成为问题,那么我将编写一个删除重复项并增加计数器的作业。 【参考方案1】:

考虑以下方法。

在更新数据的过程中将简单快速的INSERT 插入auto_update_log。在此阶段不要关心 auto_update_log 中的重复项。

有另一个后台进程定期(每隔几分钟,或任何适合您的系统)汇总auto_update_log 中累积的内容,并使用不重复的简明摘要更新最终的log 表。使用MERGE 使用适当的支持索引更新摘要。将auto_update_log 添加到摘要中后对其进行清理。

换句话说,auto_update_log 成为允许批量更新摘要的临时临时表。摘要中的信息会延迟,因此您需要决定是否可以接受。


使用您在问题中提出的示例MERGE 语句,我会考虑在(id, item_a_before, item_a_after) 上添加索引 - 用于比较的那些字段。或者只是在 id 加上 item_a_before, item_a_after 作为包含的列。在两个auto_update_log和您的临时表#changes中。

拥有索引可以加快查找必须更新的行的过程,但在添加行时更新索引本身需要时间。因此,最终,您需要尝试衡量不同方法的性能。

【讨论】:

这基本上是我的备份计划。我打算创建一个作业/SP,它使用 CTE 对这些字段进行分区并识别重复项,然后更新原始行的计数器并删除重复项,或者根据 CTE 的内容重写表。这项工作只需要每晚打扫。我只是更喜欢清理数据,而不是事后清理它,但这没什么大不了的——我已经花了太多时间在这上面。感谢您的建议,我会将其标记为答案,因为这就是我要做的。 您可以先尝试简单地添加索引,如我在答案第二部分中所述。在任何情况下,您都需要衡量更改前后的性能。

以上是关于如何使用多列索引优化 MERGE的主要内容,如果未能解决你的问题,请参考以下文章

MySQL-优化之 index merge(索引合并)

MySQL 优化之 index merge(索引合并)

MySQL 优化之 index_merge (索引合并)

merge函数为啥没有作为索引的一样的列名字会不一样

MySQL 中的多列索引如何工作?

如何在 Erlang Mnesia 中创建和使用(或模拟)多列索引