Sql server 使用SQL Server创建跨计算机互斥体

Sql server 使用SQL Server创建跨计算机互斥体,sql-server,sql-server-2008,synchronization,locking,Sql Server,Sql Server 2008,Synchronization,Locking,我有几台计算机使用相同的数据库(SQL Server 2008) 我正在尝试使用数据库在所有这些计算机之间同步任务 每个任务都由一个guid表示,该guid是锁id(如果与互斥体比较,则为互斥体名称) 我有一些想法,但我认为它们是一种黑客,我希望这里的人会有更好的解决方案: 创建一个新表“锁定”每一行由一个guid组成,在事务中以独占方式锁定表行,并在事务完成时完成/恢复事务 在锁名称为锁id guid的事务中使用sp_getapplock 我认为保持事务运行不是很好。。。我想也许有一种解决方案

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

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

每个任务都由一个guid表示,该guid是锁id(如果与互斥体比较,则为互斥体名称)

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

  • 创建一个新表“锁定”每一行由一个guid组成,在事务中以独占方式锁定表行,并在事务完成时完成/恢复事务
  • 在锁名称为锁id guid的事务中使用
    sp_getapplock

  • 我认为保持事务运行不是很好。。。我想也许有一种解决方案不需要我持有打开的事务或会话?

    我会推荐一些完全不同的解决方案:。不要显式锁定任务,而是将任务添加到处理队列中,让队列处理程序将任务出列并执行工作。额外的解耦还将有助于可伸缩性和吞吐量。

    如果您仅有的共享资源是数据库,那么在解决方案中使用事务锁可能是您的最佳选择。如果我理解另一个答案中由@Remus Rusanu链接的文章,它还要求在事务中退出队列

    这在某种程度上取决于您计划打开这些锁的时间。如果你是

  • 强制序列化以对有问题的锁ID执行简短操作
  • 已打开事务以完成该操作
  • …那么您的选项2可能是最简单、最可靠的。我已经在一个生产系统中有了这样一个解决方案好几年了,没有任何问题。如果将互斥体的创建与事务的创建捆绑在一起,并将其全部封装在一个“using”块中,就更容易了


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

    我已经创建了一个小类将测试和反馈

    用途是:

    GlobalMutex globalMutex = new GlobalMutex(
        new SqlConnection(""),
        "myGlobalUniqueLockName",
        new TimeSpan(0, 1, 0)
    );
    
    
    using (globalMutex.Lock)
    {
        // do work.
    }
    

    我知道把人们推荐给谷歌不是一个好的做法,但我认为这是一个有效的例外。如果OP选择这样做,他可以在谷歌上搜索“分布式同步”,并找到很多关于该主题的好资源。感谢您的回复,但是这个主题通常非常复杂,并且与服务器之间的数据同步(复制、合并等)更相关。我只想在这些计算机之间持有一个独占锁。我唯一拥有的共享资源是一个数据库,我需要使用这个数据库获得一个独占锁。我无法在这些计算机之间创建共享/同步队列。抱歉。但不幸的是,这对我仍然没有帮助。这是一个用户发起的任务-它发生在其中一台计算机上,当它运行时,我不希望用户能够在任何其他计算机上发起任务。我不明白排队对我有什么帮助,我明白了。使用会话绑定的applock(而不是事务绑定的applock)应该可以满足您的需要。如果sp返回<0,我会抛出一个异常。请参阅-btw处的返回代码值。ExecuteOnQuery的结果不是sp的结果
    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.
    }