提问者:小点点

SQL服务器-防止丢失更新和死锁


我正在开发一个项目,其中有2个应用程序是用C#开发的(.NET框架4)-

  1. WCF服务(暴露给客户)
  2. ASP.NETWeb表单应用程序(总部使用)。

这两个应用程序都可以从SQLServer 2005数据库中的公共“帐户”表中选择和更新行。帐户表中的每一行都包含一个客户余额。

两个应用程序中请求的业务逻辑都涉及从“账户”表中选择一行,根据余额进行一些处理,然后更新数据库中的余额。选择和更新余额之间的过程不能参与事务。

我意识到在选择行和更新它之间是可能的,该行可以被来自相同或不同应用程序的另一个请求选择和更新。

我在下面的文章中发现了这个问题。我指的是"丢失更新"的第二种情况。http://www.codeproject.com/Articles/342248/Locks-and-Duration-of-Transactions-in-MS-SQL-Serve

第二种情况是当一个事务(事务A)读取一条记录并将该值检索到一个局部变量中,该记录将被另一个事务(事务B)更新。稍后事务A将使用局部变量中的值更新记录。在这种情况下,事务B所做的更新可以被视为“丢失的更新”。

我正在寻找一种方法来防止上述情况,并防止如果收到对同一行的多个并发请求,则余额变为负值。一行应在同一时间仅由单个请求(来自任一应用程序)选择和更新,以确保余额一致。

我的想法是,一旦一个请求选择了一行,就立即阻止对它的访问。基于我的研究,下面是我的观察结果。

  1. 隔离级别

使用可重复读取隔离级别,两个事务可以选择一个公共行。

我测试了这是打开2个SSMS窗口。在这两个窗口中,我启动了一个具有可重复读取隔离级别的事务,然后在公共行上进行选择。我能够在每个事务中选择该行。

接下来,我尝试从每个事务更新同一行。语句持续运行几秒钟。然后,从第一个事务的更新成功,而从第二个事务的更新失败,并带有以下消息。

错误1205:事务(进程ID)在锁资源上与另一个进程死锁,并被选为死锁受害者。重新运行事务。

因此,如果我使用具有可重复读取的事务,则2个并发事务不可能更新同一行。Sql服务器自动选择回滚1个事务。这是正确的吗?

但我也希望通过允许仅由单个事务选择特定行来避免死锁错误。

我在Stackoverflow上找到了下面的答案,其中提到使用ROWLOCK提示来防止死锁。

避免“丢失更新”的最低事务隔离级别

我启动了一个事务,并使用了带有ROWLOCKUPDLOCK的select语句。然后在一个新的SSMS窗口中,我启动了另一个事务,并尝试使用相同的select查询(具有相同的锁)。这次我无法选择行。语句一直在新的SSMS窗口中运行。

因此,在事务中使用Rowlock似乎会阻止使用相同锁提示的select语句的行。

如果有人能回答以下问题,我将不胜感激。

>

  • 我对隔离级别和行锁的观察是否正确?

    对于我描述的场景,我应该使用ROWLOCKUPDLOCK提示来阻止对行的访问吗?如果不是,正确的方法是什么?

    我计划将我的选择和更新代码放在事务中。事务中的第一个选择查询将使用ROWLOCKUPDLOCK提示。这将防止记录被另一个使用具有相同锁的select检索同一行的事务选择。


  • 共1个答案

    匿名用户

    我建议SQLSNAPSHOT的隔离级别。与甲骨文锁管理非常相似。

    看http://www.databasejournal.com/features/mssql/snapshot-isolation-level-in-sql-server-what-why-and-how-part-1.html

    如果你的代码不是太复杂,你可能可以在没有任何变化的情况下实现这一点。要记住,一些可见性可能会受到影响(即脏读可能不会给出脏数据)

    我发现这个一揽子系统比到处使用查询提示更容易、更精确。

    使用以下方式配置数据库:

    SET ALLOW_SNAPSHOT_ISOLATION ON
    

    然后使用它来为您的交易语句添加前缀:

    SET TRANSACTION ISOLATION LEVEL SNAPSHOT