我使用MERGE
功能使用批量导入表作为源将数据插入表中。 (as described here)
这是我的疑问:
DECLARE @InsertMapping TABLE (BulkId int, TargetId int);
MERGE dbo.Target T
USING dbo.Source S
ON 0=1 WHEN NOT MATCHED THEN
INSERT (Data) VALUES (Data)
OUTPUT S.Id BulkId, inserted.Id INTO @InsertMapping;
通过显示实际执行计划来评估性能时,我发现主键索引上有一个高成本排序。我不明白,因为主键应该已经按升序排序,不需要额外的排序。
由于此排序成本,查询需要几秒钟才能完成。有没有办法加快插入?也许某些指数暗示或其他指数?即使有几千个条目,这样的插入也不应该花那么长时间。
答案 0 :(得分:2)
我可以使用以下
重现此问题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
属性是根据预期要插入的行数设置的。保罗怀特解释in the comments here
添加了[DMLRequestSort]以支持最小化日志的能力 2008年INSERT语句。最小化的前提条件之一 记录是将行呈现给Insert运算符 群集密钥顺序。
无论如何,以聚簇索引键顺序插入表可以更有效,因为它可以减少随机IO和碎片。
如果函数getconditionalidentity
按升序返回生成的标识值(看起来似乎合理),那么排序的输入将已按所需顺序排列。在这种情况下,计划中的排序在逻辑上是多余的(以前有similar issue与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;
这会减少估计的行数,并且计划不再有排序。您需要测试此实际是否可以提高性能。可能会让事情变得更糟。