Web应用批量插入同列值场景下表排他锁(含读操作)方案问询
解决批次插入两行同列值数据的并发问题
你的思路精准命中了并发场景的核心痛点——避免多会话同时读取MAX值导致重复,用TABLOCKX加HOLDLOCK的锁策略方向完全正确,不过咱们可以把细节补得更严谨,同时也给你提供一些适配不同场景的替代思路:
一、完善你的锁表方案
首先要处理表为空的边界情况:如果表D还没有任何数据,MAX(ID)会返回NULL,直接加1还是NULL,插入时会报错。所以必须用ISNULL兜底。下面是完整的存储过程示例:
-- 先创建表(你的原代码) CREATE TABLE D(Id int, Name varchar(100)) -- 完整的存储过程 CREATE PROCEDURE InsertTwoMatchingIdRows AS BEGIN SET NOCOUNT ON; -- 避免返回影响行数的额外信息 BEGIN TRAN; -- 用TABLOCKX获取表级排他锁,HOLDLOCK保持锁到事务结束,彻底阻止其他读写 DECLARE @newId int = ISNULL((SELECT MAX(ID) FROM D WITH (TABLOCKX, HOLDLOCK)), 0) + 1; -- 插入两行ID相同的数据 INSERT INTO D (Id, Name) VALUES (@newId, 'Batch Row 1'); INSERT INTO D (Id, Name) VALUES (@newId, 'Batch Row 2'); COMMIT TRAN; END;
锁策略说明:
TABLOCKX:对整个表施加排他锁,任何其他会话的读、写操作都会被阻塞,完全杜绝了脏读和并发修改的可能。HOLDLOCK(等同于SERIALIZABLE隔离级别):把锁的持有时间延长到事务提交或回滚,确保从读取MAX值到插入数据的整个过程中,锁不会提前释放。
二、高并发场景的替代方案
如果你的系统写入并发量较高,表级排他锁可能会导致其他操作阻塞,这时可以考虑用**序列(Sequence)**来生成ID,不需要锁表也能保证每次拿到唯一的递增ID,然后用这个ID插入两行:
-- 创建序列(SQL Server 2012+、PostgreSQL、MySQL 8.0+均支持) CREATE SEQUENCE IdSequence START WITH 1 INCREMENT BY 1; -- 存储过程实现 CREATE PROCEDURE InsertTwoMatchingIdRows_WithSequence AS BEGIN SET NOCOUNT ON; -- 只获取一次序列值 DECLARE @newId int = NEXT VALUE FOR IdSequence; -- 插入两行相同ID的数据 INSERT INTO D (Id, Name) VALUES (@newId, 'Sequence Row 1'); INSERT INTO D (Id, Name) VALUES (@newId, 'Sequence Row 2'); END;
序列的优势是完全无锁,并发性能更好,但前提是你的数据库支持序列特性。如果必须依赖表中已有的MAX值来生成新ID,那还是锁表的方案更直接。
内容的提问来源于stack exchange,提问作者Anas
相关产品推荐
相关产品推荐

