能见度警告:不要其他答案。它会给出不正确的值。继续阅读为什么它是错误的。
UPDATE
鉴于在 SQL Server 2008 R2中工作所需的复杂性OUTPUT
,我将查询更改为:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
到:
SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid
UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid
基本上我停止使用OUTPUT
. 这还不错,因为Entity Framework 本身也使用了同样的 hack!
希望2012 2014 2016 2018 2019 2020 会有更好的实施。
更新:使用 OUTPUT 是有害的
我们开始的问题是尝试使用该OUTPUT
子句来检索表中的“之后”值:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
然后达到了SQL Server 中众所周知的限制(“不会修复”错误):
如果语句包含没有 INTO 子句的 OUTPUT 子句,则 DML 语句的目标表“BatchReports”不能有任何启用的触发器
解决方法尝试 #1
所以我们尝试了一些我们将使用中间TABLE
变量来保存OUTPUT
结果的方法:
DECLARE @t TABLE (
LastModifiedDate datetime,
RowVersion timestamp,
BatchReportID int
)
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid
SELECT * FROM @t
除了因为不允许将 atimestamp
插入表(甚至是临时表变量)而失败。
解决方法尝试 #2
我们偷偷知道 atimestamp
实际上是一个 64 位(又名 8 字节)的无符号整数。我们可以将临时表定义更改为使用binary(8)
而不是timestamp
:
DECLARE @t TABLE (
LastModifiedDate datetime,
RowVersion binary(8),
BatchReportID int
)
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid
SELECT * FROM @t
这行得通,除了值是错误的。
我们返回的时间戳RowVersion
不是 UPDATE 完成后时间戳的值:
- 返回时间戳:
0x0000000001B71692
- 实际时间戳:
0x0000000001B71693
这是因为OUTPUT
我们表中的值不是UPDATE 语句末尾的值:
- UPDATE 语句开始
- 修改行
- OUTPUT 检索新的时间戳(即 3)
- 触发运行
- UPDATE 语句完成
- OUTPUT 返回3 (错误值)
这表示:
- 我们没有得到时间戳,因为它存在于 UPDATE 语句(4)的末尾
- 相反,我们得到时间戳,因为它位于 UPDATE 语句(3)的不确定中间
- 我们没有得到正确的时间戳
修改行中任何值的任何触发器也是如此。不会在 UPDATE 结束时输出值。OUTPUT
这意味着您不能相信 OUTPUT 会返回任何正确的值。
这个痛苦的现实记录在 BOL 中:
从 OUTPUT 返回的列反映了在 INSERT、UPDATE 或 DELETE 语句完成之后但在触发器执行之前的数据。
Entity Framework 是如何解决的?
.NET Entity Framework 使用 rowversion 实现乐观并发。EF 依赖于在timestamp
发布 UPDATE 后知道它存在的值。
由于您不能OUTPUT
用于任何重要数据,Microsoft 的 Entity Framework 使用与我相同的解决方法:
解决方法 #3 - 最终 - 不要使用 OUTPUT 子句
为了检索after值,实体框架发出:
UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))
SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1
不要使用OUTPUT
.
是的,它受到竞争条件的影响,但这是 SQL Server 可以做的最好的事情。
那么 INSERT 呢?
做实体框架做的事情:
SET NOCOUNT ON;
DECLARE @generated_keys table([CustomerID] int)
INSERT Customers (FirstName, LastName)
OUTPUT inserted.[CustomerID] INTO @generated_keys
VALUES ('Steve', 'Brown')
SELECT t.[CustomerID], t.[CustomerGuid], t.[RowVersion], t.[CreatedDate]
FROM @generated_keys AS g
INNER JOIN Customers AS t
ON g.[CustomerGUID] = t.[CustomerGUID]
WHERE @@ROWCOUNT > 0
同样,他们使用SELECT
语句来读取行,而不是信任 OUTPUT 子句。