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

确保批量插入数据的唯一批次ID:并发场景下的疑问与优化建议

问题描述

我通过VBA实现了多用户从各自工作站独立向SQL Server单表并发批量插入数据的流程,Excel生成的SQL脚本仅包含一条INSERT INTO语句,示例如下:

INSERT INTO TABLE (col1, col2)
VALUES (a,b), (c,d), (e,f);

我为该表编写了如下INSTEAD OF INSERT触发器,用于为每个插入批次生成唯一的batchID和供下游使用的extraID。想请教:在多用户并发操作时,当前锁机制下batchID是否可能出现重复?如果存在问题,能否给出优化建议?

触发器代码:

CREATE TRIGGER [dbo].[example] 
ON [dbo].[tblExample]
INSTEAD OF INSERT
AS 
BEGIN 
    BEGIN TRANSACTION;

    DECLARE @batchID AS int
    DECLARE @extraID AS int

    SELECT @batchID = COALESCE(MAX(BatchID), 0) + 1  
    FROM tblExample WITH (TABLOCKX, HOLDLOCK)
    
    SELECT @extraID = COALESCE(MAX(nodeID), 0) + 1 
    FROM tblExample WITH (TABLOCKX, HOLDLOCK)

    INSERT INTO [tblExample] WITH (TABLOCK)
        SELECT @batchID, @extraID, col1, col2 
        FROM inserted 
    
    COMMIT TRANSACTION;
END
问题解答

1. 当前实现下batchID是否会重复?

不会重复。你在查询MAX(BatchID)时使用了TABLOCKX(排他表锁)和HOLDLOCK(将锁持有到事务结束),这会强制所有并发事务排队执行:当第一个事务持有表的排他锁时,其他事务必须等待该事务提交后才能获取锁并计算新的batchID,因此不会出现多个事务同时拿到相同MAX(BatchID)的情况。

但这种方案存在明显的性能问题:排他表锁会完全阻塞表上的所有读写操作,在高并发场景下会导致严重的排队等待,系统吞吐量大幅下降。

2. 优化建议

(1)使用SQL Server序列(SEQUENCE)替代MAX()+1

序列是SQL Server 2012及以上版本支持的对象,专门用于生成连续/非连续的唯一标识,天生支持并发,不需要手动加锁:

-- 创建batchID序列
CREATE SEQUENCE dbo.BatchID_Seq
    AS INT
    START WITH 1
    INCREMENT BY 1;

-- 创建extraID序列
CREATE SEQUENCE dbo.ExtraID_Seq
    AS INT
    START WITH 1
    INCREMENT BY 1;

-- 修改触发器
CREATE TRIGGER [dbo].[example] 
ON [dbo].[tblExample]
INSTEAD OF INSERT
AS 
BEGIN 
    DECLARE @batchID AS int
    DECLARE @extraID AS int

    -- 获取序列值
    SET @batchID = NEXT VALUE FOR dbo.BatchID_Seq;
    SET @extraID = NEXT VALUE FOR dbo.ExtraID_Seq;

    INSERT INTO [tblExample]
        SELECT @batchID, @extraID, col1, col2 
        FROM inserted 
END

优势:无需手动管理锁,并发性能大幅提升,序列值生成是原子操作,天然保证唯一。

(2)使用单独的控制表存储当前最大ID

创建一张专门的控制表来存储batchID和extraID的当前最大值,然后用UPDLOCK+HOLDLOCK替代表级排他锁,缩小锁范围:

-- 创建控制表
CREATE TABLE dbo.ID_Control (
    ID_Type VARCHAR(20) PRIMARY KEY,
    Current_Value INT NOT NULL DEFAULT 0
);

-- 初始化数据
INSERT INTO dbo.ID_Control (ID_Type, Current_Value)
VALUES ('BatchID', 0), ('ExtraID', 0);

-- 修改触发器
CREATE TRIGGER [dbo].[example] 
ON [dbo].[tblExample]
INSTEAD OF INSERT
AS 
BEGIN 
    DECLARE @batchID AS int
    DECLARE @extraID AS int

    -- 获取并更新BatchID
    UPDATE dbo.ID_Control WITH (UPDLOCK, HOLDLOCK)
    SET Current_Value = Current_Value + 1,
        @batchID = Current_Value + 1
    WHERE ID_Type = 'BatchID';

    -- 获取并更新ExtraID
    UPDATE dbo.ID_Control WITH (UPDLOCK, HOLDLOCK)
    SET Current_Value = Current_Value + 1,
        @extraID = Current_Value + 1
    WHERE ID_Type = 'ExtraID';

    INSERT INTO [tblExample]
        SELECT @batchID, @extraID, col1, col2 
        FROM inserted 
END

优势:锁的范围缩小到控制表的单行记录,而非整个业务表,并发性能比原方案提升很多。

(3)触发器简化优化

原触发器中的BEGIN TRANSACTION可以去掉,因为触发器本身会在隐式事务中执行(除非显式指定SET IMPLICIT_TRANSACTIONS ON),显式事务会增加不必要的开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 12:47:46