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。犯罪

我真正想从事务中实现的是,当线程 A 选择特定记录时,线程 B 甚至应该无法选择该记录。我不知道我是否通过在这种情况下使用事务和锁来思考正确的轨道。

等待回复:)

4

4 回答 4

5

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

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

如果没有这个,您可能会遇到死锁,因为默认情况下 select 采用共享读锁:

  1. 事务 A 执行选择(共享读锁)。
  2. 事务 B 执行选择(共享读锁,可能在与事务 A 相同的一些记录上,例如,如果采用页锁)。
  3. 事务 A 现在进行更新,这需要排他写锁(锁升级),但必须等待事务 B 释放其共享读锁。
  4. 事务 B 现在也想做它的更新,但必须等待事务 A 释放它的共享读锁。

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

  1. 事务 A 执行选择(独占更新锁)。
  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 自己选择锁的范围,它通常做得很好;在这种情况下,它不应该使用表锁。只有在没有它的情况下才显式使用行锁。

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

于 2012-09-27T08:37:14.033 回答
1

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

Optimistic_concurrency_control 维基百科

于 2012-09-27T07:52:55.693 回答
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,就像我在上面所做的那样 - 如果您可以将所有逻辑推送到存储过程中,这样您只需调用 proc 并且数据库中处理的所有逻辑都这样.

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

Microsoft.Practices.EnterpriseLibrary.Data;

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

于 2012-10-21T00:15:16.417 回答
-1

在我看来,您应该查看正在使用的线程机制。您应该能够预先知道(而不是在事务期间)并且不使用已经处理的 ID 启动线程。或者您的线程应该有权访问一些共享同步列表,其中包含应处理的 ID。这样两个线程就不能在同一个 ID 上工作。

于 2012-09-27T08:02:35.563 回答