使用DistributedLock nuget的SQL分布式锁Dispose后未释放问题
问题根因
- 核心问题是你所有分布式锁操作都复用了同一个全局静态SqlConnection实例,两个关键原因导致故障:
- SqlServer 提供的
sp_getapplock(DistributedLock 库底层依赖的原语)是会话级别的锁,锁的生命周期和所属的数据库连接(会话)直接绑定,同一连接会话持有锁时,同会话下的其他锁请求会被判定为重入,锁引用计数会叠加,单次Dispose只会减少计数不会真正释放锁,导致后续其他请求永远拿不到锁。 - ADO.NET 的
SqlConnection本身不是线程安全类型,多个异步任务并发操作同一个连接实例,会触发未定义行为,进一步导致锁释放指令执行异常。
- SqlServer 提供的
修复方案
去掉共用连接的逻辑,改为每次需要操作锁时创建独立的SqlConnection,ADO.NET内置的连接池会自动管理连接开销,不会有额外性能损耗,修正后的代码如下:
class Program { // 替换全局连接为连接字符串 private static string _connStr = "my connection string"; static void Main(string[] args) { int pid; using (var p = Process.GetCurrentProcess()) { pid = p.Id; } TestSqlLocking(pid).Wait(); } private static async Task TestSqlLocking(int processId) { var t1 = DoWorkAsyncWithSqlLock($"Process {processId} Client 1"); var t2 = DoWorkAsyncWithSqlLock($"Process {processId} Client 2"); var t3 = DoWorkAsyncWithSqlLock($"Process {processId} Client 3"); var t4 = DoWorkAsyncWithSqlLock($"Process {processId} Client 4"); await Task.WhenAll(t1, t2, t3, t4); } private static async Task DoWorkAsyncWithSqlLock(string client) { // 每个任务独立创建连接 await using var sqlConn = new SqlConnection(_connStr); await sqlConn.OpenAsync(); var sqlDistributedLock = new SqlDistributedLock("myLock", sqlConn); Console.WriteLine($"SQL Before taking the lock for client [{client}]"); await using (await sqlDistributedLock.AcquireAsync(TimeSpan.FromSeconds(10))) { Console.WriteLine($"SQL After taking the lock and doing some work for client [{client}]"); await Task.Delay(1000); Console.WriteLine($"SQL After doing some work and releasing lock for client [{client}]"); } } }
- 补充说明:如果你的场景确实需要复用连接,必须确保同一时间只有一个线程操作该连接,且锁操作串行执行,这种方案性能会大幅下降,不推荐使用。
内容的提问来源于stack exchange,提问作者Michał Turczyn
相关产品推荐
相关产品推荐

