【问题标题】:Transactions and Locks事务和锁
【发布时间】:2012-09-27 07:47:01
【问题描述】:

我目前正在处理交易并感到困惑。这些事务是在数据访问层而不是在数据库的存储过程中创建的 (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 甚至应该无法选择该记录。我不知道我是否正在考虑在这种情况下使用事务和锁是否走在正确的轨道上。

等待回复:)

【问题讨论】:

  • 一般来说,线程B在线程A之后立即更新记录对您来说可以吗? IE。线程 A 所做的更改将丢失。如果这不适合您,那么您应该研究乐观并发控制。
  • 线程B不能更新记录。我什至想防止在事务中已被 A 选择的记录被选择。

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


【解决方案1】:

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

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

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

  1. 事务 A 执行选择(共享读锁)。
  2. 事务 B 执行选择(共享读锁,可能在某些 与事务 A 相同的记录,例如,如果采取了页面锁定)。
  3. 事务 A 现在进行更新,这需要独占写入 lock(锁升级),但必须等待事务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 自己选择锁的范围,它通常做得很好;在这种情况下,它不应该使用表锁。只有在没有它的情况下才显式使用行锁。

另请注意,行锁本身不会阻止两个事务选择相同记录(行)然后尝试更新它的死锁 - 因此您始终需要一个更新锁。

【讨论】:

  • updlock 将锁定已选择的特定行还是锁定整个表或页面锁定。 ID 是主键,因此它始终只选择一条记录。如果它只锁定特定行,那么这就是我一直在寻找的解决方案。
【解决方案2】:

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

Optimistic_concurrency_control wikipedia

【讨论】:

  • 我不确定这在这种特殊情况下是否有帮助。乐观锁定将使线程 B 能够执行 ID=1 的 Select Employee,而无需等待来自线程 A 的事务。最后,线程 B 也会更新员工。
  • 乐观并发控制允许多个事务访问一个记录并在它没有被修改的情况下对其进行更改。但在我的情况下,我不想让多个事务甚至选择相同的记录。
【解决方案3】:

试试这样的:

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

【讨论】:

  • 我自己将数据块用于所有与数据库相关的功能。您给出的场景,在 if 条件内我无法直接设置状态,我调用另一个提供响应的 Web 服务,并且基于该响应我必须更新状态。而且由于它是一个可能需要时间的网络请求,并且在此期间另一个请求可能会进入我的服务器并选择相同的记录并尝试使用它。我想停止这个其他请求,甚至对同一条记录执行选择。
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-06
    相关资源
    最近更新 更多