交易和锁

时间:2012-09-27 07:47:02

标签: c# sql-server-2008 transactions locks

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

  1. 发起交易
  2. 选择ID = 1的员工。
  3. 更新ID = 1的员工。
  4. 提交
  5. 有多个线程执行相同但不同的ID。但是可能存在两个线程中查找相同ID的情况。让我们称它们为线程A和B.上述步骤以下列方式针对两个线程进行。隔离级别设置为可重复读取。

    A1。发起交易 A2。选择ID = 1的员工。 B1。发起交易 B2。选择ID = 1的员工。 A3。更新ID = 1的员工。 A4。承诺 B3。更新ID = 1的员工。 B4。提交

    我真正希望从事务中实现的是,当线程A选择特定记录时,线程B甚至不能选择该记录。我不知道在这种情况下使用事务和锁是否正在考虑正确的轨道。

    等待回复:)

4 个答案:

答案 0 :(得分:1)

你应该看看乐观锁定,它通过在更新中添加额外的检查来工作,在那里检查读取和写入之间的记录是否没有改变。您还可以在事务范围之外读取记录,从而为您提供更好的整体性能。

Optimistic_concurrency_control wikipedia

答案 1 :(得分: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释放其共享读锁。
  5. 所以事务A和B现在正在等待彼此 - 经典的锁升级死锁。 UPDLOCK表提示避免了这种情况,因为它强制select选择进行独占锁定:

    1. 事务A执行select(独占更新锁定)。
    2. 事务B想要执行其选择,但必须等待事务A首先释放其锁定。
    3. 事务A现在执行更新,并提交,释放select选择的更新锁。
    4. 交易B现在可以进行选择。
    5. 编辑:您可以将UPDLOCK与ROWLOCK组合以请求行级别锁定,例如“with(updlock,rowlock)”。你可以问,但你可能并不总是这样 - 请参阅http://msdn.microsoft.com/en-us/library/ms187373(v=sql.100).aspx。行锁也可能比页锁更昂贵,因为如果使用行锁,SQL Server可能会有更多的锁跟踪。所以我会让SQL Server为自己选择锁定的程度,它通常做得很好;在这种情况下,它不应该采取表锁。如果没有它,只有明确使用行锁。

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

答案 2 :(得分: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;

那就是因为我倾向于从他们的企业库中使用MS的数据块,它在OOTB库之上提供了大量功能。如果您有兴趣,可以在此处详细了解:http://msdn.microsoft.com/library/cc467894.aspx

答案 3 :(得分:0)

我觉得你应该看看你正在使用的线程机制。您应该能够预先知道(而不是在事务期间)并且不能启动具有已经处理的ID的线程。或者你的线程应该可以访问一些具有应该处理的ID的共享同步列表。这样两个线程就无法处理相同的ID。