2012-09-27 244 views
0

我目前正在处理交易和变得困惑。这些事务是在数据访问层创建的,而不是在数据库的存储过程中创建的(SQL Server 2008)。 我了解为交易设置的隔离级别的正常工作。 我无法理解在以下情况下会发生什么情况。交易和锁

  1. 发起交易
  2. 与ID = 1选择的雇员。
  3. 更新ID = 1的员工。
  4. 提交

有多个线程在做同样的事情,但不同的ID。但可能会出现两个线程查找相同ID的情况。让我们称它们为线程A和B.上述步骤按照以下两种线程的方式进行。隔离级别设置为可重复读取。

A1。开始交易 A2。选择ID = 1的员工。 B1。开始交易 B2。选择ID = 1的员工。 A3。更新ID = 1的员工。 A4。提交 B3。更新ID = 1的员工。 B4。提交

我真正想从事务中实现的是,当线程A选择特定记录时,线程B甚至不应该能够选择该记录。我不知道我是否通过使用事务和锁来解决这个问题。

等待答复:)

+0

一般来说,线程B在线程A之后立即更新记录是否可以?即线程A所做的更改将会丢失。如果这不适合你,那么你应该研究乐观的并发控制。 – Alexander

+0

线程B更新记录并不正确。我甚至想阻止A在交易中被A选中时被选中。 – Sharkz

回答

1

您应该使用UPDLOCK表提示,以防止死锁,例如,

select * from employee with (updlock) where id = @id 
update employee set name = @name where id = @id 

如果没有这个,你可以得到死锁,因为默认选择需要共享读锁:

  1. 事务A做select(共享读锁)。
  2. 事务B执行select(共享读锁,可能在与事务A相同的记录(例如,如果执行页面锁定)的某些 中)。
  3. 事务A现在执行更新,这需要锁定(锁定升级)的独占写入 ,但必须等待事务B释放其共享读锁定 。
  4. 事务B现在也想更新它,但必须等待事务A释放其共享读锁。

因此,事务A和B现在正在彼此等待 - 经典锁升级死锁。 UPDLOCK表提示避免了这种情况,因为它强制选择采取排他锁:

  1. 事务A执行select(独占更新锁定)。
  2. 事务B想要做它的选择,但必须等待事务A首先释放它的锁。
  3. 事务A现在执行更新并提交,释放select所采用的更新锁定。
  4. 交易B现在可以进行选择。

编辑:您可以将UPDLOCK和ROWLOCK组合起来,以请求行级锁定,例如“with(updlock,rowlock)”。你可以问,但你可能并不总是得到它 - 见http://msdn.microsoft.com/en-us/library/ms187373(v=sql.100).aspx。同样,行锁可能比页锁更昂贵,因为如果使用行锁,SQL Server可能会有更多的锁来跟踪。所以我会让SQL Server为自己选择锁的范围,它通常是确定的工作;在这种情况下,它不应该采取表锁。只有明确地使用一个行锁,如果你有没有它的问题。

另请注意,自己的rowlock不会阻止死锁,其中两个事务选择相同的记录(行),然后尝试更新它 - 所以你总是需要一个updlock。

+0

updlock是否锁定已选择的特定行或将锁定整个表或页锁。 ID是主键,因此它总是只选择一条记录。如果它只锁定那一行,那么这就是我一直在寻找的解决方案。 – Sharkz

+0

请参阅我的编辑。 – Polyfun

1

你应该看看乐观锁定,它的工作原理是你在哪里检查记录并不在读取和写入之间改变的更新增加了额外的检查。您还可以在事务处理范围之外阅读您的记录,从而为您提供更好的整体表现。

Optimistic_concurrency_control wikipedia

+0

我不确定这是否有助于这种特殊情况。乐观锁定将使线程B能够执行Select ID = 1的雇员,而无需等待来自线程A的事务。最后,线程B也会更新雇员。 – OttO

+0

乐观并发控制允许多个事务访问一条记录,并在未修改的情况下对其进行更改。但在我的情况下,我不想让多次交易甚至选择相同的记录。 – Sharkz

0

我看来,你应该看看线程你使用机制。您应该能够知道最前面的内容(不是在交易过程中),也不能使用已经处理的ID启动线程。或者,线程应该有权访问一些带有应该处理的ID的共享同步列表。这样两个线程不能在相同的ID上工作。

1

尝试这样:

using System; 
using System.Transactions; 
using System.Data; 
using Microsoft.Practices.EnterpriseLibrary.Data; 

namespace StackOverflow.Demos 
{ 
    class Program 
    { 

     static Database db = DatabaseFactory.CreateDatabase("demo"); 

     public static void Main(string[] args) 
     { 
      TransactionOptions options = new TransactionOptions(); 
      options.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead; //see http://www.gavindraper.co.uk/2012/02/18/sql-server-isolation-levels-by-example/ for a helpful guide to choose as per your requirements 
      using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options)) 
      { 
       using (IDbConnection connection = db.CreateConnection()) 
       { 
        connection.Open(); //nb: connection must be openned within transactionscope in order to take part in the transaction 
        IDbCommand command = connection.CreateCommand(); 

        command.CommandType = CommandType.Text; 
        command.CommandText = "select top 1 status from someTable with(UPDLOCK, ROWLOCK) where id = 1"; //see http://msdn.microsoft.com/en-us/library/aa213026(v=sql.80).aspx 
        string statusResult = command.ExecuteScalar().ToString(); 

        if (!statusResult.Equals("closed",StringComparison.OrdinalIgnoreCase)) 
        { 
         command.CommandType = CommandType.Text; 
         command.CommandText = "update someTable set status='closed' where id = 1"; 
        } 

        scope.Complete(); 
       } 
      } 
     } 
    } 
} 

PS。一般建议您像上面所做的那样使用硬编码SQL的存储过程 - 如果您可以将所有逻辑推送到存储过程中,以便您只需调用一次proc,并在数据库中处理所有逻辑即可。

在上面的例子中,你会发现命名空间:

Microsoft.Practices.EnterpriseLibrary.Data; 

这是那里,因为我倾向于从自己的企业库,它给你的功能,负载上的OOTB库的顶部使用MS的数据块。如果你有兴趣,你可以在这里阅读更多关于:http://msdn.microsoft.com/library/cc467894.aspx

+0

我自己使用所有数据库相关功能的数据块。在if条件中我不能直接设置状态的情况下,我调用另一个提供响应的Web服务,并基于该响应我必须更新状态。而且由于它的网络请求可能需要时间,并且在此期间,另一个请求可能会进入我的服务器并选择相同的记录并尝试使用它。我想停止这个其他请求,甚至在相同的记录上执行选择。 – Sharkz