【问题标题】:Creating a cross-computer mutex using SQL Server使用 SQL Server 创建跨计算机互斥锁
【发布时间】:2012-08-13 14:23:52
【问题描述】:

我有几台计算机使用相同的数据库 (SQL Server 2008)

我正在尝试使用数据库在所有这些计算机之间同步任务。

每个任务都由一个 guid 表示,该 guid 是 lock-id(如果与 Mutex 比较,那就是 Mutex 名称)

我有一些想法,但我认为它们是一种黑客行为,并希望这里有人能提供更好的解决方案:

  1. 创建一个新表“锁定”每行包含一个 guid,将表行以独占方式锁定在事务中 - 并在完成后完成/恢复事务。
  2. 在锁定名称为锁定 ID GUID 的事务中使用 sp_getapplock

我认为保持事务运行不太好......我想也许有一个解决方案不需要我保持打开的事务或会话?

【问题讨论】:

  • 我知道将人们推荐给 Google 并不是一个好习惯,但我认为这是一个有效的例外。如果 OP 选择这样做,他可以谷歌“分布式同步”并找到很多关于该主题的好资源。
  • 感谢您的回复,但是这个主题通常非常复杂,并且与服务器之间的同步数据(复制、合并等)有关。我只想在这些计算机之间持有一个排他锁。

标签: sql-server sql-server-2008 synchronization locking


【解决方案1】:

我已经组织了一个小班将测试和反馈

public class GlobalMutex
{
    private SqlCommand _sqlCommand;
    private SqlConnection _sqlConnection;

    string sqlCommandText = @"

        declare @result int
        exec @result =sp_getapplock @Resource=@ResourceName, @LockMode='Exclusive', @LockOwner='Transaction', @LockTimeout = @LockTimeout

    ";

    public GlobalMutex(SqlConnection sqlConnection, string unqiueName, TimeSpan lockTimeout)
    {
        _sqlConnection = sqlConnection;
        _sqlCommand = new SqlCommand(sqlCommandText, sqlConnection);
        _sqlCommand.Parameters.AddWithValue("@ResourceName", unqiueName);
        _sqlCommand.Parameters.AddWithValue("@LockTimeout", lockTimeout.TotalMilliseconds);
    }

    private readonly object _lockObject = new object();
    private Locker _lock = null;
    public Locker Lock
    {
        get
        {
            lock(_lockObject)
            {
                if (_lock != null)
                {
                    throw new InvalidOperationException("Unable to call Lock twice"); // dont know why
                }
                _lock = new Locker(_sqlConnection, _sqlCommand);
            }
            return _lock;
        }
    }

    public class Locker : IDisposable
    {
        private SqlTransaction _sqlTransaction;
        private SqlCommand _sqlCommand;

        internal Locker(SqlConnection sqlConnection, SqlCommand sqlCommand)
        {
            _sqlCommand = sqlCommand;
            _sqlTransaction = sqlConnection.BeginTransaction();
            _sqlCommand.Transaction = _sqlTransaction;
            int result = sqlCommand.ExecuteNonQuery();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing) 
            {
                _sqlTransaction.Commit(); // maybe _sqlTransaction.Rollback() might be slower
            }
        }
    }
}

用法是:

GlobalMutex globalMutex = new GlobalMutex(
    new SqlConnection(""),
    "myGlobalUniqueLockName",
    new TimeSpan(0, 1, 0)
);


using (globalMutex.Lock)
{
    // do work.
}

【讨论】:

  • 如果 sp 返回 docs.microsoft.com/en-us/sql/relational-databases/… 的返回代码值 - 顺便说一句。 ExecuteNonQuery 的结果不是 sp 的结果
【解决方案2】:

我会推荐一些不同的东西:use a queue。与其显式锁定任务,不如将任务添加到处理队列并让队列处理程序将任务出列并执行工作。额外的解耦也将有助于可扩展性和吞吐量。

【讨论】:

  • 我唯一的共享资源是一个数据库,我需要使用这个数据库获得一个排他锁。我无法在这些计算机之间创建共享/同步队列。
  • 对不起。但不幸的是,这仍然对我没有帮助。这是用户启动的任务 - 它发生在其中一台计算机上,并且在运行时我不希望用户能够在任何其他计算机上启动任务。我看不出队列对我有什么帮助。
  • 我明白了。使用会话绑定应用锁(与事务绑定应用锁相反)应该可以满足您的需求。
【解决方案3】:

如果您拥有的唯一共享资源是数据库,那么使用事务锁作为解决方案的一部分可能是您的最佳选择。如果我在另一个答案中理解@Remus Rusanu 链接的文章,它还需要在事务中进行出队。

这在一定程度上取决于您打算打开这些锁多久。如果你是……

  1. 强制序列化以对相关锁 ID 进行简短操作
  2. 无论如何已经打开交易以完成该操作

...那么您的选项 2 可能是最简单和最可靠的。几年来,我在生产系统中都有类似的解决方案,没有任何问题。如果您将互斥锁的创建与事务的创建捆绑在一起并将其全部包装在“使用”块中,则会变得更加容易。

using (var transaction = MyTransactionUtil.CreateTransaction(mutexName))
{
    // do stuff
    transaction.Commit();
}

在您的 CreateTransaction 实用程序方法中,您在创建事务后立即调用 sp_getapplock。然后整个事情(包括互斥体)一起提交或回滚。

【讨论】:

    猜你喜欢
    • 2011-07-16
    • 2020-11-07
    • 1970-01-01
    • 1970-01-01
    • 2016-08-04
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多