我正在开发一个项目,其中有2个应用程序是用C#开发的(.NET框架4)-
这两个应用程序都可以从SQLServer 2005数据库中的公共“帐户”表中选择和更新行。帐户表中的每一行都包含一个客户余额。
两个应用程序中请求的业务逻辑都涉及从“账户”表中选择一行,根据余额进行一些处理,然后更新数据库中的余额。选择和更新余额之间的过程不能参与事务。
我意识到在选择行和更新它之间是可能的,该行可以被来自相同或不同应用程序的另一个请求选择和更新。
我在下面的文章中发现了这个问题。我指的是"丢失更新"的第二种情况。http://www.codeproject.com/Articles/342248/Locks-and-Duration-of-Transactions-in-MS-SQL-Serve
第二种情况是当一个事务(事务A)读取一条记录并将该值检索到一个局部变量中,该记录将被另一个事务(事务B)更新。稍后事务A将使用局部变量中的值更新记录。在这种情况下,事务B所做的更新可以被视为“丢失的更新”。
我正在寻找一种方法来防止上述情况,并防止如果收到对同一行的多个并发请求,则余额变为负值。一行应在同一时间仅由单个请求(来自任一应用程序)选择和更新,以确保余额一致。
我的想法是,一旦一个请求选择了一行,就立即阻止对它的访问。基于我的研究,下面是我的观察结果。
使用可重复读取隔离级别,两个事务可以选择一个公共行。
我测试了这是打开2个SSMS窗口。在这两个窗口中,我启动了一个具有可重复读取隔离级别的事务,然后在公共行上进行选择。我能够在每个事务中选择该行。
接下来,我尝试从每个事务更新同一行。语句持续运行几秒钟。然后,从第一个事务的更新成功,而从第二个事务的更新失败,并带有以下消息。
错误1205:事务(进程ID)在锁资源上与另一个进程死锁,并被选为死锁受害者。重新运行事务。
因此,如果我使用具有可重复读取的事务,则2个并发事务不可能更新同一行。Sql服务器自动选择回滚1个事务。这是正确的吗?
但我也希望通过允许仅由单个事务选择特定行来避免死锁错误。
我在Stackoverflow上找到了下面的答案,其中提到使用ROWLOCK
提示来防止死锁。
避免“丢失更新”的最低事务隔离级别
我启动了一个事务,并使用了带有ROWLOCK
和UPDLOCK
的select语句。然后在一个新的SSMS窗口中,我启动了另一个事务,并尝试使用相同的select查询(具有相同的锁)。这次我无法选择行。语句一直在新的SSMS窗口中运行。
因此,在事务中使用Rowlock似乎会阻止使用相同锁提示的select语句的行。
如果有人能回答以下问题,我将不胜感激。
>
我对隔离级别和行锁的观察是否正确?
对于我描述的场景,我应该使用ROWLOCK
和UPDLOCK
提示来阻止对行的访问吗?如果不是,正确的方法是什么?
我计划将我的选择和更新代码放在事务中。事务中的第一个选择查询将使用ROWLOCK
和UPDLOCK
提示。这将防止记录被另一个使用具有相同锁的select检索同一行的事务选择。
我建议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