EF Core Insert 操作——SQL 命令是如何工作的?

ati*_*yar 1 sql sql-server sql-insert

下面的 SQL 命令是如何工作的?

exec sp_executesql N'SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [OrderLine] USING (
VALUES (@p1, @p2, 0),
(@p3, @p4, 1),
(@p5, @p6, 2),
(@p7, @p8, 3)) AS i ([Item], [OrderId], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Item], [OrderId])
VALUES (i.[Item], i.[OrderId])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;

SELECT [t].[Id] FROM [OrderLine] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];

',N'@p1 nvarchar(64),@p2 int,@p3 nvarchar(64),@p4 int,@p5 nvarchar(64),@p6 int,@p7 nvarchar(64),@p8 int',@p1=N'Item-1',@p2=1,@p3=N'Item-2',@p4=1,@p5=N'Item-3',@p6=1,@p7=N'Item-4',@p8=1
Run Code Online (Sandbox Code Playgroud)

(它由 EF Core 生成并插入了一些OrderLine实体)。

编辑:
我了解 TABLE 类型变量的声明并且对 MERGE 操作有基本的了解。但是很难理解实际数据是如何以及何时插入到 OrderLine 表中的。

Pan*_*vos 5

这是一个有趣的问题,值得一整篇文章来回答。幸运的是,Brent Ozar 写了The Case of Entity Framework Core's Odd SQL

MERGE 语句基本上做了 anINSERT ... OUTPUT inserted.ID VALUES (),(),()会做的事情。该子句ON 1=0确保仅执行 INSERT 分支。那么为什么会有如此复杂的语法呢?

这种奇怪的 SQL 的原因是批量插入的性能。具体来说,10K 行的性能提高了 248%。

在表中插入多行的方法只有几种:

  • 您可以批量编写 5000 个 INSERT 查询,但这很。几乎比单个大 INSERT 慢 5000 倍,因为每个语句都必须修改索引等。
  • 您可以传递表类型参数。但这也很慢,因为服务器无法知道该参数中有多少项,并且假设只有一行。这可能会导致非常糟糕的执行计划。
  • 存储在表变量中?与表参数相同的问题
  • 您可以先将所有这些行写入临时表,然后将它们插入到目标中,但这有其自身的问题 - 表应该命名什么?它是独一无二的吗?

身份证呢?

table 变量用于收集生成的 id,_Position即使在并行执行的情况下也用于维护顺序。我怀疑,这就是为什么INSERT VALUES也没有使用的原因。

由于 EF Core 仅在单个批次中发送了 5000 个项目,因此它需要一种方法来检索 5000 个新 ID,以允许它识别这些 ID 属于哪些对象。通常,人们会使用 ID 来标识行,但没有 ID 开始!

剩下的唯一事情就是按照插入对象的相同顺序返回 ID。INSERT OUTPUT VALUES不能保证-没有一个ORDER子句,该服务器可以自由地在可能的最便宜的方式返回数据。

在这种情况下,保留 ID 和顺序的唯一安全方法是将它们与显式_Position值一起存储@inserted0并按该顺序返回它们。

结论

这是关于性能的,正如 Brent Ozar 所说:

所以是的,SQL 并不完美,但它的速度提高了 248%。

布伦特说:呼,孩子。这……不理想。