依赖 INSERT 的 OUTPUT 子句的顺序是否安全?

Eri*_*ikE 22 sql-server insert identity sql-server-2012 bulk-insert

鉴于此表:

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);
Run Code Online (Sandbox Code Playgroud)

在两个稍微不同的场景中,我想插入行并从标识列返回值。

场景一

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
   (VALUES
      ('Blue', 'New', 1234),
      ('Blue', 'Cancel', 4567),
      ('Red', 'New', 5678)
   ) t (Color, Action, Code)
;
Run Code Online (Sandbox Code Playgroud)

场景二

CREATE TABLE #Target (
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL,
   Code int NOT NULL,
   PRIMARY KEY CLUSTERED (Color, Action)
);

-- Bulk insert to the table the same three rows as above by any means

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;
Run Code Online (Sandbox Code Playgroud)

我是否可以依赖从dbo.Target表插入返回的标识值以它们在 1)VALUES子句和 2)#Target表中存在的顺序返回,以便我可以通过它们在输出行集中的位置将它们关联回原始输入?

以供参考

下面是一些精简的 C# 代码,用于演示应用程序中发生的情况(场景 1,即将转换为使用SqlBulkCopy):

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 26

我可以依赖从 dbo.Target 表插入返回的标识值以它们在 1) VALUES 子句和 2) #Target 表中存在的顺序返回,以便我可以通过它们在输出行集中的位置来关联它们到原始输入?

不,如果没有实际的书面保证,您不能依赖任何保证。文档明确指出没有这样的保证。

SQL Server 不保证使用 OUTPUT 子句的 DML 语句处理和返回行的顺序。由应用程序包含一个适当的 WHERE 子句来保证所需的语义,或者理解当多行可能符合 DML 操作的条件时,没有保证的顺序。

这将依赖于许多未记录的假设

  1. 从常量扫描输出行的顺序与 values 子句的顺序相同(我从未见过它们不同,但 AFAIK 这不能保证)。
  2. 行插入的顺序将与它们从常量扫描中输出的顺序相同(绝对并非总是如此)。
  3. 如果使用“宽”(每个索引)执行计划,则输出子句中的值将从聚集索引更新操作符而不是任何二级索引的值中提取。
  4. 保证此后保留订单 - 例如,当打包行以通过网络传输时
  5. 即使顺序现在看起来是可预测的,对并行插入等功能的实现更改也不会在将来更改顺序(目前,如果在 INSERT…SELECT 语句中指定了 OUTPUT 子句以将结果返回给客户端,则并行计划是一般禁用,包括插入

(Color, Action)如果向该VALUES子句添加 600 行,则可以看到第二点失败的示例(假设集群 PK 为)。然后该计划在插入之前有一个排序运算符,因此在VALUES子句中丢失了原始顺序。

有一种实现目标的记录方法,这是向源添加编号并使用MERGE而不是INSERT

MERGE dbo.Target
USING (VALUES (1, 'Blue', 'New', 1234),
              (2, 'Blue', 'Cancel', 4567),
              (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ON 1 = 0
WHEN NOT MATCHED THEN
  INSERT (Color,
          Action,
          Code)
  VALUES (Color,
          Action,
          Code)
OUTPUT t.SourceId,
       inserted.TargetId; 
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

@a_horse_with_no_name

合并真的有必要吗?你就不能做一个insert into ... select ... from (values (..)) t (...) order by sourceid吗?

是的,你可以。SQL Server 中的排序保证......指出

使用 SELECT 和 ORDER BY 来填充行的 INSERT 查询保证了标识值的计算方式,但不保证行插入的顺序

所以你可以使用

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES (1, 'Blue', 'New', 1234),
        (2, 'Blue', 'Cancel', 4567),
        (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ORDER BY t.SourceId
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这将保证标识值按顺序分配t.SourceId但不以任何特定顺序输出,或者分配的标识列值没有间隙(例如,如果尝试并发插入)。

  • 最后一点关于差距的可能性和输出没有按特定顺序使尝试与输入相关联变得更有趣。我认为应用程序中的 order by 可以完成这项工作,但仅使用 `MERGE` 似乎更安全、更清晰。 (2认同)