Sql事务的并发处理
Posted
技术标签:
【中文标题】Sql事务的并发处理【英文标题】:Concurrency handling of Sql transactrion 【发布时间】:2010-04-17 13:08:48 【问题描述】:假设,我即将开始一个使用 ASP.NET 和 SQL Server 2005 的项目。我必须设计此应用程序的并发要求。我打算在每个表中添加一个时间戳列。在更新表格时,我将检查 TimeStamp 列是否与选中时相同。
这种方法就足够了吗?或者在任何情况下这种方法有什么缺点吗?
请指教。
谢谢
李乔
【问题讨论】:
可能不是一个好主意 - 看看这篇文章mssqltips.com/tip.asp?tip=1501 谢谢。它给了一些光。一个问题 - 为什么我们说在悲观条件下使用锁定比使用时间戳更好?假设 user1 读到它并走出办公室,其他用户必须等待很长时间才能收到错误消息,对吧?如果我们使用时间戳方法,我们不能得到即时回复吗?这里缺少什么链接? “悲观条件”意味着锁定 - 这就是为什么没有多少知道自己在做什么的人使用它 让我重新表述我的问题。文章说“悲观并发是在用户频繁发生冲突的假设下工作的”。为什么在发生冲突的可能性很高时应用锁定?应用锁定后,其他用户是否必须等待很长时间才能收到错误消息? 【参考方案1】:首先,您在问题中描述的方式是我认为 最好的方式 用于将 MS SQL 作为数据库的 ASP.NET 应用程序。数据库中没有锁定。它非常适合永久断开连接的客户端(例如 Web 客户端)。
如何从一些答案中阅读,术语存在误解。我们的意思是使用 Microsoft SQL Server 2008 或更高版本来保存数据库。如果您在 MS SQL Server 2008 文档中打开主题“rowversion (Transact-SQL)”,您会发现以下内容:
"时间戳是 rowversion 数据类型,受制于 数据类型同义词的行为。” ... "timestamp 语法已弃用。 此功能将在 Microsoft SQL 的未来版本 服务器。避免在 新的开发工作,并计划 修改当前使用的应用程序 这个功能。”
所以 timestamp 数据类型是 MS SQL 的 rowversion 数据类型的同义词。它拥有每个数据库内部存在的 64 位计数器,可以看作 @@DBTS。修改数据库的一张表中的一行后,计数器会递增。
在阅读您的问题时,我将“TimeStamp”读作 rowversion 数据类型的列名。我个人更喜欢 RowUpdateTimeStamp 这个名称。在 AzManDB(请参阅 Microsoft Authorization Manager with the Store as DB)中,我可以看到这样的名称。有时还使用 ChildUpdateTimeStamp 来跟踪分层 RowUpdateTimeStamp 结构(关于触发器)。
我在上一个项目中实施了这种方法并且非常高兴。通常您会执行以下操作:
-
将 RowUpdateTimeStamp 列添加到数据库的每个表中,类型为 rowversion(在 Microsoft SQL Management Studio 中将显示为 timestamp,同理)。
您应该构造所有用于将结果发送到客户端的 SQL SELECT 查询,以便将附加的 RowVersion 值与主要数据一起发送。如果您有一个带有 JOINT 的 SELECT,您应该从两个表中发送 RowVersion 的最大 RowUpdateTimeStamp 值,例如
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN s.RowUpdateTimeStamp
ELSE m.RowUpdateTimeStamp
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
或者像下面这样进行数据转换
SELECT s.Id AS Id
,s.Name AS SoftwareName
,m.Name AS ManufacturerName
,CASE WHEN s.RowUpdateTimeStamp > m.RowUpdateTimeStamp
THEN CAST(s.RowUpdateTimeStamp AS bigint)
ELSE CAST(m.RowUpdateTimeStamp AS bigint)
END AS RowUpdateTimeStamp
FROM dbo.Software AS s
INNER JOIN dbo.Manufacturer AS m ON s.Manufacturer_Id=m.Id
将RowUpdateTimeStamp保存为bigint,对应C#的ulong数据类型。如果您从许多表中创建 OUTER JOINT 或 JOINT,则所有表中的构造 MAX(RowUpdateTimeStamp)
将看起来更复杂一些。由于 MS SQL 不支持像 MAX(a,b,c,d,e) 这样的函数,因此相应的构造可能如下所示:
(SELECT MAX(rv)
FROM (SELECT table1.RowUpdateTimeStamp AS rv
UNION ALL SELECT table2.RowUpdateTimeStamp
UNION ALL SELECT table3.RowUpdateTimeStamp
UNION ALL SELECT table4.RowUpdateTimeStamp
UNION ALL SELECT table5.RowUpdateTimeStamp) AS maxrv) AS RowUpdateTimeStamp
-
所有断开连接的客户端(Web 客户端)不仅接收并保存某些数据行,还接收并保存数据行的 RowVersion(类型 ulong)。
在尝试从断开连接的客户端修改数据时,您的客户端应将与原始数据对应的 RowVersion 发送到服务器。
spSoftwareUpdate
存储过程可能看起来像
CREATE PROCEDURE dbo.spSoftwareUpdate
@Id int,
@SoftwareName varchar(100),
@originalRowUpdateTimeStamp bigint, -- used for optimistic concurrency mechanism
@NewRowUpdateTimeStamp bigint OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- ExecuteNonQuery() returns -1, but it is not an error
-- one should test @NewRowUpdateTimeStamp for DBNull
SET NOCOUNT ON;
UPDATE dbo.Software
SET Name = @SoftwareName
WHERE Id = @Id AND RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
SET @NewRowUpdateTimeStamp = (SELECT RowUpdateTimeStamp
FROM dbo.Software
WHERE (@@ROWCOUNT > 0) AND (Id = @Id));
END
dbo.spSoftwareDelete
存储过程的代码看起来是一样的。如果不开启NOCOUNT
,会在很多场景中自动产生DBConcurrencyException。 Visual Studio 为您提供了使用乐观并发的可能性,例如 TableAdapter
或 DataAdapter
的高级选项中的“使用乐观并发”复选框。
如果您查看dbo.spSoftwareUpdate
存储过程,您会发现,我在WHERE 中使用RowUpdateTimeStamp <= @originalRowUpdateTimeStamp
而不是RowUpdateTimeStamp = @originalRowUpdateTimeStamp
。我这样做是因为,具有客户端的@originalRowUpdateTimeStamp
的值通常被构造为来自多个表的MAX(RowUpdateTimeStamp)
。所以它可以是RowUpdateTimeStamp < @originalRowUpdateTimeStamp
。您应该使用严格相等 = 并在此处重现与您在 SELECT 语句中使用的相同的复杂 JOIN 语句,或者像我一样使用 构造并保持与以前相同的安全性.
顺便说一句,可以基于 RowUpdateTimeStamp 为 ETag 构造一个非常好的值,该值可以在 HTTP 标头中与数据一起发送给客户端。使用ETag,您可以在客户端实现智能数据缓存。
我不能在这里写完整的代码,但是你可以在网上找到很多例子。我只想再重复一次,在我看来,使用基于 rowversion 的乐观并发是 大多数 ASP.NET 场景的最佳方式 .
【讨论】:
感谢您的详细回答,但是,存储过程代码看起来不正确或不完整。在哪里 Id=@Id AND RowUpdateTimeStamp 0) ?我知道已经 3 年了,但我只是偶然发现了这一点。 @Mike:谢谢,这是一个旧的“剪切和粘贴错误”,错误地修改了代码!我更新了存储过程dbo.spSoftwareUpdate
的代码。我希望现在代码是正确的。【参考方案2】:
在 SQL Server 中,针对情况类型的推荐方法是创建 'rowversion' 类型的列,并使用它来检查该行中的任何字段是否已更改。
SQL Server 保证如果行中的任何值发生更改(或插入新行),它的 rowversion 列将自动更新为不同的值。让数据库为您处理这件事比自己尝试要可靠得多。
在您的更新语句中,您只需添加一个 where 子句来检查 rowversion 值是否与您第一次检索该行时的值相同。如果不是,则其他人已更改该行(即:它很脏)
另外,从那个页面:
不推荐使用时间戳语法。 此功能将在 Microsoft SQL 的未来版本 服务器。避免在 新的开发工作,并计划 修改当前使用的应用程序 这个功能。
【讨论】:
【参考方案3】:我不确定是否应该像这样在数据库中处理并发。数据库本身应该能够管理隔离和事务行为,但线程行为应该在代码中完成。
【讨论】:
他没有提到线程。数据库必须能够处理并发访问——无论是来自单个多线程应用程序还是来自多个应用程序实例,或者两者兼而有之,既不存在也不存在。 那么他需要关心的是酸和隔离。【参考方案4】:rowversion 的建议是正确的,我会说,但令人失望的是,时间戳很快就会被弃用。我的一些旧应用程序出于不同的原因使用它,然后检查并发性。
【讨论】:
以上是关于Sql事务的并发处理的主要内容,如果未能解决你的问题,请参考以下文章