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


19

给定此表:

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)
);

在两种略有不同的方案中,我想插入行并从标识列返回值。

场景1

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)
;

方案2

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
;

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

以供参考

以下是一些精简的C#代码,它们演示了应用程序中的情况(场景1,即将转换为use SqlBulkCopy):

public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
   var targetList = targets.ToList();
   const string insertSql = @"
      INSERT dbo.Target (
         CoreItemId,
         TargetDateTimeUtc,
         TargetTypeId,
      )
      OUTPUT
         Inserted.TargetId
      SELECT
         input.CoreItemId,
         input.TargetDateTimeUtc,
         input.TargetTypeId,
      FROM
         (VALUES
            {0}
         ) input (
            CoreItemId,
            TargetDateTimeUtc,
            TargetTypeId
         );";
   var results = Connection.Query<DbTargetInsertResult>(
      string.Format(
         insertSql,
         string.Join(
            ", ",
            targetList
               .Select(target => $@"({target.CoreItemId
                  }, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
                  }', {(byte) target.TargetType
                  })";
               )
         )
      )
      .ToList();
   return targetList
      .Zip( // The correlation that relies on the order of the two inputs being the same
         results,
         (inputTarget, insertResult) => new Target(
            insertResult.TargetId, // with the new TargetId to replace null.
            inputTarget.TargetDateTimeUtc,
            inputTarget.CoreItemId,
            inputTarget.TargetType
         )
      )
      .ToList()
      .AsReadOnly();
}

Answers:


22

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

不,没有实际的书面保证,您不能依靠任何保证。该文档明确声明不存在此类保证。

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

这将取决于许多未记录的假设

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

(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; 

在此处输入图片说明

@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

在此处输入图片说明

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


2
关于间隙潜力和输出未按特定顺序排列的最后一点使尝试关联回输入变得更加有趣。我想通过应用程序中的命令可以完成任务,但是仅使用似乎更安全,更清晰MERGE
ErikE

使用OUTPUT ... INTO [#temp]语法,然后SELECT ... FROM [#temp] ORDER BY保证输出顺序。
Max Vernon

TL; DR版本:使用SQL Server,并且我相信一般来说SQL实现都是这样,除非有ORDER BY子句,否则不能保证顺序。
nateirvin '16

回覆。差距:将Insert声明附在后面以Transaction防止差距吗?
汤姆(Tom)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.