我不完全确定,但我觉得这个问题真的是关于 upsert 的,也就是下面的原子操作:
- 如果源和目标中都存在该行,则
UPDATE目标;
- 如果该行只存在于源中,则
INSERT该行进入目标;
- (可选)如果行存在于目标中但不源中,
DELETE 来自目标的行。
开发人员出身的 DBA 经常天真地逐行编写,如下所示:
-- For each row in source
IF EXISTS(<target_expression>)
IF @delete_flag = 1
DELETE <target_expression>
ELSE
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
ELSE
INSERT target (<target_columns>)
VALUES (<source_values>)
这几乎是你能做的最糟糕的事情,有几个原因:
它有一个竞争条件。该行可以在IF EXISTS 和随后的DELETE 或UPDATE 之间消失。
这很浪费。对于每笔交易,您都会执行额外的操作;也许这很微不足道,但这完全取决于您的索引情况。
最糟糕的是 - 它遵循迭代模型,在单行级别考虑这些问题。这将对整体性能产生最大(最差)的影响。
一个很小的(我强调次要的)优化就是尝试UPDATE。如果该行不存在,@@ROWCOUNT 将为 0,然后您可以“安全地”插入:
-- For each row in source
BEGIN TRAN
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
IF (@@ROWCOUNT = 0)
INSERT target (<target_columns>)
VALUES (<source_values>)
COMMIT
在最坏的情况下,这仍然会为每个事务执行两个操作,但至少有一个机会只执行一个,并且它还消除了竞争条件(有点)。
但真正的问题是源代码中的每一行仍在执行此操作。
在 SQL Server 2008 之前,您必须使用笨拙的 3 阶段模型在集合级别处理此问题(仍然比逐行好):
BEGIN TRAN
INSERT target (<target_columns>)
SELECT <source_columns> FROM source s
WHERE s.id NOT IN (SELECT id FROM target)
UPDATE t SET <target_columns> = <source_columns>
FROM target t
INNER JOIN source s ON t.d = s.id
DELETE t
FROM target t
WHERE t.id NOT IN (SELECT id FROM source)
COMMIT
正如我所说,这方面的性能相当糟糕,但仍然比一次一行的方法好很多。然而,SQL Server 2008 终于引入了MERGE 语法,所以现在你要做的就是:
MERGE target
USING source ON target.id = source.id
WHEN MATCHED THEN UPDATE <target_columns> = <source_columns>
WHEN NOT MATCHED THEN INSERT (<target_columns>) VALUES (<source_columns>)
WHEN NOT MATCHED BY SOURCE THEN DELETE;
就是这样。一种说法。如果您使用的是 SQL Server 2008 并且需要执行 INSERT、UPDATE 和 DELETE 的任何序列,具体取决于该行是否已经存在 - 即使它只是一行 - 没有借口不使用MERGE。
如果您以后需要了解所做的事情,您甚至可以将受MERGE 影响的行OUTPUT 放入表变量中。简单、快速、无风险。去做吧。