我可以用以下方法重现此问题
CREATE TABLE dbo.TargetTable(Id int IDENTITY PRIMARY KEY, Value INT)
CREATE TABLE dbo.BulkTable(Id int IDENTITY PRIMARY KEY, Value INT)
INSERT INTO dbo.BulkTable
SELECT TOP (1000000) 1
FROM sys.all_objects o1, sys.all_objects o2
DECLARE @TargetTableMapping TABLE (BulkId INT,TargetId INT);
MERGE dbo.TargetTable T
USING dbo.BulkTable S
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (Value)
VALUES (Value)
OUTPUT S.Id AS BulkId,
inserted.Id AS TargetId
INTO @TargetTableMapping;
这在聚集索引合并运算符之前给出了一个排序计划。
排序是Expr1011, Action1010
从先前的运算符输出的两个计算列。
Expr1011
是调用内部和未记录的函数为 中的标识列getconditionalidentity
生成id
列的结果TargetTable
。
Action1010
是指示插入、更新、删除的标志。它总是4
在这种情况下,因为该MERGE
语句可以执行的唯一操作是INSERT
。
排序在计划中的原因是聚集索引合并运算符具有 DMLRequestSort 属性集。
该DMLRequestSort
属性是根据预期插入的行数设置的。保罗怀特在这里的评论中解释
2008 年添加了 [DMLRequestSort] 以支持最少记录 INSERT 语句的能力。最少记录的前提条件之一是行以集群键顺序呈现给 Insert 运算符。
无论如何,以聚集索引键顺序插入表会更有效,因为它减少了随机 IO 和碎片。
如果函数getconditionalidentity
以升序返回生成的标识值(这似乎是合理的),那么排序的输入将已经是所需的顺序。在这种情况下,计划中的排序在逻辑上是冗余的,(以前有一个类似的问题,即 NEWSEQUENTIALID 的不必要排序)
可以通过使表达式更加不透明来摆脱这种排序。
DECLARE @TargetTableMapping TABLE (BulkId INT,TargetId INT);
DECLARE @N BIGINT = 0x7FFFFFFFFFFFFFFF
MERGE dbo.TargetTable T
USING (SELECT TOP(@N) * FROM dbo.BulkTable) S
ON 1=0
WHEN NOT MATCHED THEN
INSERT (Value)
VALUES (Value)
OUTPUT S.Id AS BulkId,
inserted.Id AS TargetId
INTO @TargetTableMapping;
这会减少估计的行数,并且计划不再有排序。不过,您将需要测试这是否真的能提高性能。可能这会让事情变得更糟。