You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多用户客户端应用中如何防止同一ID的dosomething Sub并发执行?

解决多客户端并发执行同一ID的dosomething方法的方案

针对多客户端(多个应用实例)场景下,防止同一ID的dosomething方法被并发执行,你提到的SyncLock确实无法解决问题——它只能控制单个应用进程内的同步,跨进程的并发必须依赖数据库层面的同步机制。下面提供两种可行的方案:

方案1:使用SQL Server应用锁(sp_getapplock)

这是SQL Server内置的应用级锁机制,无需额外创建表,直接针对特定ID生成唯一锁键,确保同一时间只有一个客户端能持有该锁。

实现代码

Public Sub dosomething(ByVal ID As Integer)
    Dim connString As String = "你的数据库连接字符串"
    Using conn As New SqlConnection(connString)
        conn.Open()
        Using tran As SqlTransaction = conn.BeginTransaction()
            Try
                ' 获取针对当前ID的排他锁,超时时间设为5000毫秒(可根据需求调整)
                Dim lockCmd As New SqlCommand("EXEC sp_getapplock @Resource = @LockKey, @LockMode = 'Exclusive', @LockTimeout = 5000", conn, tran)
                lockCmd.Parameters.AddWithValue("@LockKey", "ID_Processing_Lock_" & ID)
                Dim lockResult As Integer = CInt(lockCmd.ExecuteScalar())

                ' 锁获取成功(返回值为0或1表示成功)
                If lockResult >= 0 Then
                    ' --------------------------
                    ' 执行你的核心业务逻辑:删除+插入表B
                    ' --------------------------
                    ' 示例:删除表B中关联当前ID的记录
                    Dim deleteCmd As New SqlCommand("DELETE FROM B WHERE ID_A = @ID", conn, tran)
                    deleteCmd.Parameters.AddWithValue("@ID", ID)
                    deleteCmd.ExecuteNonQuery()

                    ' 示例:插入表B的逻辑(根据表A数据生成)
                    ' Dim insertCmd As New SqlCommand("INSERT INTO B (ID_A, ...) VALUES (@ID, ...)", conn, tran)
                    ' insertCmd.Parameters.AddWithValue("@ID", ID)
                    ' insertCmd.ExecuteNonQuery()

                    ' 释放锁
                    Dim releaseCmd As New SqlCommand("EXEC sp_releaseapplock @Resource = @LockKey", conn, tran)
                    releaseCmd.Parameters.AddWithValue("@LockKey", "ID_Processing_Lock_" & ID)
                    releaseCmd.ExecuteNonQuery()

                    tran.Commit()
                Else
                    Throw New InvalidOperationException($"无法获取ID {ID} 的执行锁,当前有其他客户端正在处理该ID")
                End If
            Catch ex As Exception
                tran.Rollback()
                Throw ex
            End Try
        End Using
    End Using
End Sub

注意事项

  • @LockTimeout:设置等待锁的超时时间,避免客户端无限等待
  • 锁必须在事务内使用,确保异常时能自动释放锁
  • 锁键要足够唯一,避免和其他业务的应用锁冲突

方案2:优化你的临时表锁方案

你提出的“用临时表存ID作为主键”思路是可行的,这里做一些优化,确保逻辑更严谨:

步骤1:创建锁表

CREATE TABLE ID_Processing_Locks (
    ID INT PRIMARY KEY,
    LockedAt DATETIME DEFAULT GETDATE()
)

实现代码

Public Sub dosomething(ByVal ID As Integer)
    Dim connString As String = "你的数据库连接字符串"
    Using conn As New SqlConnection(connString)
        conn.Open()
        ' 使用Serializable隔离级别,确保锁的排他性
        Using tran As SqlTransaction = conn.BeginTransaction(IsolationLevel.Serializable)
            Try
                ' 尝试插入锁记录,主键重复则触发异常
                Dim lockCmd As New SqlCommand("INSERT INTO ID_Processing_Locks (ID) VALUES (@ID)", conn, tran)
                lockCmd.Parameters.AddWithValue("@ID", ID)
                lockCmd.ExecuteNonQuery()

                ' --------------------------
                ' 执行你的核心业务逻辑:删除+插入表B
                ' --------------------------
                ' 示例:删除表B中关联当前ID的记录
                Dim deleteCmd As New SqlCommand("DELETE FROM B WHERE ID_A = @ID", conn, tran)
                deleteCmd.Parameters.AddWithValue("@ID", ID)
                deleteCmd.ExecuteNonQuery()

                ' 示例:插入表B的逻辑(根据表A数据生成)
                ' Dim insertCmd As New SqlCommand("INSERT INTO B (ID_A, ...) VALUES (@ID, ...)", conn, tran)
                ' insertCmd.Parameters.AddWithValue("@ID", ID)
                ' insertCmd.ExecuteNonQuery()

                ' 释放锁:删除锁记录
                Dim unlockCmd As New SqlCommand("DELETE FROM ID_Processing_Locks WHERE ID = @ID", conn, tran)
                unlockCmd.Parameters.AddWithValue("@ID", ID)
                unlockCmd.ExecuteNonQuery()

                tran.Commit()
            Catch ex As SqlException
                ' 捕获主键重复异常(SQL错误号2627)
                If ex.Number = 2627 Then
                    Throw New InvalidOperationException($"ID {ID} 正在被其他客户端处理,请稍后重试")
                Else
                    tran.Rollback()
                    Throw ex
                End If
            Catch ex As Exception
                tran.Rollback()
                Throw ex
            End Try
        End Using
    End Using
End Sub

优化点

  • 使用Serializable隔离级别,避免并发插入时的幻读问题
  • 捕获特定的SQL异常(错误号2627)来判断是否是并发冲突
  • 事务包裹整个流程,确保异常时锁记录能被回滚

方案对比

  • 应用锁方案:轻量,无需额外维护锁表,适合大多数场景
  • 临时表方案:直观,可通过查询锁表排查当前正在处理的ID,适合需要监控锁状态的场景

内容的提问来源于stack exchange,提问作者bautista

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 17:35:13