可见度警告:请勿回答。它将给出不正确的值。继续阅读以了解错误原因。
鉴于作出所需的杂牌UPDATE
与OUTPUT
在SQL Server 2008 R2的工作,我改变了我的查询范围:
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本身也使用了这种相同的技巧!
希望2012 2014 2016 2018 2019 2019会有更好的实施。
更新:使用输出有害
我们开始的问题是尝试使用OUTPUT
子句检索表中的“ after”值:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid
然后,这达到了SQL Server中众所周知的限制(不会修复错误):
如果DML语句的目标表'BatchReports'包含不带INTO子句的OUTPUT子句,则该语句不能具有任何启用的触发器
解决方法尝试#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
否则会失败,因为您不允许timestamp
在表中插入A (即使是临时表变量也是如此)。
解决方法尝试#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语句开始
- UPDATE语句完成
- OUTPUT返回3 (错误的值)
这表示:
- 我们没有获得时间戳,因为它存在于UPDATE语句的末尾(4)
- 相反,我们获得的时间戳是在UPDATE语句的不确定中间(3)
- 我们没有正确的时间戳
任何修改行中任何值的触发器都适用。该OUTPUT
不会输出值作为更新的结束。
这意味着您永远无法信任OUTPUT返回任何正确的值。
BOL中记录了这种痛苦的现实:
从OUTPUT返回的列反映了INSERT,UPDATE或DELETE语句完成之后但执行触发器之前的数据。
实体框架如何解决它?
.NET实体框架使用行版本进行乐观并发。EF依赖于timestamp
在发出UPDATE之后知道as的值。
由于您不能使用OUTPUT
任何重要数据,因此Microsoft的Entity Framework使用与我相同的解决方法:
解决方法#3-最终-不要使用OUTPUT子句
为了检索后值,实体框架发出:
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子句中。