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 为您提供了使用乐观并发的可能性,例如 TableAdapterDataAdapter 的高级选项中的“使用乐观并发”复选框。

如果您查看dbo.spSoftwareUpdate 存储过程,您会发现,我在WHERE 中使用RowUpdateTimeStamp &lt;= @originalRowUpdateTimeStamp 而不是RowUpdateTimeStamp = @originalRowUpdateTimeStamp。我这样做是因为,具有客户端的@originalRowUpdateTimeStamp 的值通常被构造为来自多个表的MAX(RowUpdateTimeStamp)。所以它可以是RowUpdateTimeStamp &lt; @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事务的并发处理的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL:事务锁下的并发处理(十五)

ThreadLocal 解决多线程程序的并发问题+事务处理

postgresql事务处理与并发控制

Spring 并发事务的探究

MySQL的运行模式及一些特性,引擎事务并发控制优化总结

Hibernate逍遥游记-第15章处理并发问题-001事务并发问题及隔离机制介绍