MSSQL如何实现多用户不重复查看同一组记录?
嘿,这是个典型的记录排他分配+动态分页场景,核心就是要保证分给A用户的记录,B用户拿不到;而A处理完某条记录后,自己的分页列表要自动补上新的未分配记录。我来给你梳理实现思路,以及MSSQL有没有现成方案:
先明确核心需求
- 分页查询时,已分配给其他用户且未处理的记录,不能重复分配
- 用户处理完某条记录后,自己的分页列表要自动补充新的未分配记录
- 其他用户的查询只需要排除已被分配且未处理的记录,不受其他人处理记录的影响
MSSQL内置方案情况
首先得说:MSSQL本身没有直接内置这种“动态排他分页”的现成功能,但可以借助它的一些核心特性来实现:
- 事务+行级锁:分配记录时用
UPDLOCK, ROWLOCK锁定目标记录,防止其他用户读取,适合数据量不大的场景 - 行版本控制:开启
READ COMMITTED SNAPSHOT隔离级别,让其他用户读取记录的版本,避免阻塞,但需要额外维护分配状态 - Service Broker队列:把未处理记录当成消息队列,用户每次“取消息”即分配记录,处理完就移除,但这个更偏向异步处理,可能不适合实时分页查询
基于辅助表的实现优化(结合你已有的方案)
你已经用辅助表实现了部分功能,这其实是最直接、易维护的方案——核心就是用辅助表记录每条记录的分配状态(分给谁、是否处理完),查询时过滤掉已分配未处理的记录。这里给你优化下逻辑:
1. 辅助表设计
至少要包含这些字段:
RecordId:关联主表的记录IDAssignedTo:分配的用户ID(NULL表示未分配)IsProcessed:是否已处理(bit类型,0=未处理,1=已处理)AssignTime:分配时间(用于超时回收长期未处理的记录)
2. 分配记录的存储过程逻辑
你的AllocateRecords2可以优化成这样,保证原子性和排他性:
CREATE PROCEDURE AllocateRecords2 @PageSize INT, @UserId INT AS BEGIN SET NOCOUNT ON; BEGIN TRANSACTION; -- 先回收超时未处理的记录(比如超过30分钟没处理,释放回池) UPDATE RecordAssignment SET AssignedTo = NULL, IsProcessed = 0 WHERE AssignTime < DATEADD(MINUTE, -30, GETDATE()) AND IsProcessed = 0; -- 分配未被占用的记录给当前用户 WITH UnassignedRecords AS ( SELECT TOP (@PageSize) r.Id FROM MainRecords r LEFT JOIN RecordAssignment ra ON r.Id = ra.RecordId WHERE ra.RecordId IS NULL OR (ra.AssignedTo IS NULL AND ra.IsProcessed = 0) ORDER BY r.Id -- 可根据业务调整排序规则,比如创建时间 ) MERGE INTO RecordAssignment ra USING UnassignedRecords ur ON ra.RecordId = ur.Id WHEN NOT MATCHED THEN INSERT (RecordId, AssignedTo, IsProcessed, AssignTime) VALUES (ur.Id, @UserId, 0, GETDATE()) WHEN MATCHED AND ra.AssignedTo IS NULL THEN UPDATE SET ra.AssignedTo = @UserId, ra.AssignTime = GETDATE(); -- 返回当前用户的分页数据:已分配未处理的 + 补充新的未分配记录(凑够PageSize) SELECT TOP (@PageSize) r.* FROM MainRecords r JOIN RecordAssignment ra ON r.Id = ra.RecordId WHERE ra.AssignedTo = @UserId AND ra.IsProcessed = 0 UNION ALL SELECT TOP (@PageSize - (SELECT COUNT(*) FROM RecordAssignment WHERE AssignedTo = @UserId AND IsProcessed = 0)) r.* FROM MainRecords r LEFT JOIN RecordAssignment ra ON r.Id = ra.RecordId WHERE ra.RecordId IS NULL OR (ra.AssignedTo IS NULL AND ra.IsProcessed = 0) ORDER BY r.Id; COMMIT TRANSACTION; END
3. 标记记录为已处理的逻辑
当用户处理完某条记录后,执行这个更新,释放该记录的占用:
UPDATE RecordAssignment SET IsProcessed = 1 WHERE RecordId = @ProcessedRecordId AND AssignedTo = @UserId;
这样下次用户调用分配存储过程时,会自动把新的未分配记录补到分页列表里。
4. 其他用户的查询逻辑
其他用户查询时,只需要排除已被分配且未处理的记录:
SELECT TOP (@PageSize) r.* FROM MainRecords r LEFT JOIN RecordAssignment ra ON r.Id = ra.RecordId WHERE (ra.RecordId IS NULL OR ra.IsProcessed = 1 OR ra.AssignedTo != @CurrentUserId) ORDER BY r.Id;
关键注意事项
- 事务必须加:分配记录的整个过程要放在事务里,避免多个用户同时抢到同一条记录
- 超时回收很重要:防止某个用户占用记录后一直不处理,导致记录“死锁”无法分配
- 索引要建好:给辅助表的
RecordId、AssignedTo、IsProcessed建复合索引,提升查询和更新的效率
内容的提问来源于stack exchange,提问作者NJS
相关产品推荐
相关产品推荐

