事务和锁定

3

我目前在处理事务并感到困惑。这些事务是在数据访问层创建的,而不是在数据库(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. 提交

我真正想从事务中实现的是,当Thread A选择特定记录时,Thread B甚至不能选择那条记录。 我不知道在这种情况下是否应该使用事务和锁定来进行处理。

等待回复 :)


一般来说,如果B线程在A线程之后立即更新记录,这对您是否可以接受?也就是说,A线程所做的更改将会丢失。如果您不能接受这种情况,那么您应该考虑使用乐观并发控制。 - Alexander
线程B更新记录是不可以的。我甚至想在事务中防止A已经选择了该记录时再次选择该记录。 - Sharkz
4个回答

5

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

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

没有使用UPDLOCK,可能会出现死锁,因为默认情况下select会获取共享读锁:
  1. 事务A执行select(获取共享读锁)。
  2. 事务B执行select(获取共享读锁,可能与事务A在某些记录上相同,例如如果获取了页面锁)。
  3. 现在事务A执行更新操作,需要独占写锁(锁升级),但必须等待事务B释放其共享读锁。
  4. 现在事务B也想要执行更新操作,但必须等待事务A释放其共享读锁。

因此,事务A和B现在互相等待-典型的锁升级死锁。 UPDLOCK表提示可以避免这种情况,因为它强制选择获取独占锁的select:

  1. 事务A执行select(获取独占更新锁)。
  2. 事务B想要执行其select,但必须等待事务A先释放其锁。
  3. 现在事务A执行更新并提交,释放由select获取的更新锁。
  4. 事务B现在可以执行其select。

编辑:您可以将UPDLOCK与ROWLOCK结合使用,请求行级锁定,例如“with(updlock,rowlock)”。您可以提出要求,但您可能不总是得到它-请参见http://msdn.microsoft.com/en-us/library/ms187373(v=sql.100).aspx。此外,如果使用行锁定,则行锁定可能比页面锁定更昂贵,因为如果使用行锁定,则SQL Server可能需要跟踪更多的锁。因此,我会让SQL Server自己选择锁的范围,它通常做得很好;在这种情况下,它不应获取表锁。只有在没有问题的情况下才明确使用rowlock。

还要注意,仅使用rowlock不能防止两个事务选择相同的记录(行),然后尝试更新它-因此您始终需要updlock。


updlock会锁定已选择的特定行,还是会锁定整个表或页面锁定?ID是主键,因此它始终只选择一条记录。如果它仅锁定该特定行,则这就是我一直在寻找的解决方案。 - Sharkz

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();
                }
            }
        }
    }
}

附注:通常建议使用存储过程而不是像我上面所做的硬编码SQL - 如果您可以将所有逻辑推入存储过程中,使得您只需调用一次过程并且所有逻辑都在数据库中处理,那么就采用这种方式。

在上面的示例中,您会注意到命名空间:

Microsoft.Practices.EnterpriseLibrary.Data;

这是因为我倾向于使用微软企业库中的数据块,它在OOTB库的基础上提供了大量功能。如果您感兴趣,可以在此处阅读更多信息:http://msdn.microsoft.com/library/cc467894.aspx


我自己使用数据块来处理所有与数据库相关的功能。您提供的场景中,在if条件内,我无法直接设置状态,我调用另一个Web服务来提供响应,并根据该响应更新状态。由于这是一个可能需要时间的Web请求,在此期间,另一个请求可能会进入我的服务器并选择相同的记录并尝试使用它。我想阻止这个其他请求甚至对同一记录执行选择。 - Sharkz

1
你应该了解一下乐观锁定,它通过在更新时添加额外的检查来工作,您可以检查记录在读取和写入之间是否已更改。您还可以在事务范围之外读取记录,这将为您提供更好的整体性能。

乐观并发控制 wikipedia


我不确定这是否会在这种特定情况下有所帮助。乐观锁定将使线程B能够执行选择ID = 1的员工而无需等待来自线程A的事务。最终,线程B也会更新该员工。 - OttO
乐观并发控制允许多个事务访问一条记录并对其进行更改,如果它尚未被修改。但在我的情况下,我甚至不想允许多个事务选择同一条记录。 - Sharkz

-1

我觉得你应该看一下你正在使用的线程机制。你应该能够事先知道(而不是在事务期间),并且不要启动已经被处理的ID的线程。或者你的线程应该可以访问一些共享的同步列表,其中包含应该被处理的ID。这样两个线程就不能同时处理同一个ID。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接