ASP.NET Core如何高性能查询两随机ID 不对同用户重复同ID组合
ASP.NET Core 无重复随机问题配对实现方案(高性能版)
现有问题表结构示例:
核心需求是给登录用户返回两个随机问题ID,同一用户永远不会看到重复的ID配对,同时保证高并发下的查询性能,具体实现方案如下:
一、前置存储设计
新增用户已展示问题配对表,专门记录每个用户已经看过的问题组合,避免查询时做复杂的全表排除逻辑:
- 字段设计:
UserId:关联用户表主键,标记所属用户SmallQuestionId:配对中数值更小的问题IDLargeQuestionId:配对中数值更大的问题ID
关键优化:给这张表创建
(UserId, SmallQuestionId, LargeQuestionId)的三字段联合唯一索引,一来从数据库层面阻止重复配对写入,二来校验配对是否存在的查询可以直接走索引,耗时稳定在毫秒级,不会随数据量上涨变慢。
存储时统一把两个ID按大小排序存储,避免(1,2)和(2,1)被识别成两个不同配对的问题。
二、核心查询逻辑
不要使用ORDER BY NEWID()这类全表排序的随机查询写法,数据量过万后性能会急剧下降,采用轻量偏移随机+快速校验的方案:
- 首次查询先获取问题表总记录数
var totalQuestionCount = await _context.Questions.CountAsync(); - 生成两个0到
totalQuestionCount-1范围内不重复的随机整数作为分页偏移量 - 通过主键排序+分页的方式快速取到两个随机问题ID,全程走主键索引,性能极高
// 取第一个随机问题ID var firstId = await _context.Questions .OrderBy(q => q.Id) .Skip(firstOffset) .Take(1) .Select(q => q.Id) .FirstAsync(); // 取第二个不重复的随机问题ID var secondId = await _context.Questions .OrderBy(q => q.Id) .Skip(secondOffset) .Take(1) .Select(q => q.Id) .FirstAsync(); // 统一按大小排序,匹配存储规则 var smallId = Math.Min(firstId, secondId); var largeId = Math.Max(firstId, secondId); - 校验当前配对是否已经给该用户展示过,直接查联合索引,不需要遍历历史记录
var isPairUsed = await _context.UserQuestionPairs .AnyAsync(p => p.UserId == currentUserId && p.SmallQuestionId == smallId && p.LargeQuestionId == largeId); - 如果配对已存在,重新生成随机偏移量重复上述查询即可,只要问题库总量足够,命中已用配对的概率极低,通常1-2次就能拿到可用配对,建议设置最大重试次数(比如5次),超过阈值说明该用户已经看完所有问题组合,直接返回无新问题的提示即可。
- 确认配对可用后,立刻将
(currentUserId, smallId, largeId)写入配对表,靠数据库唯一索引做并发兜底,哪怕同时发起多个请求,也不会出现重复写入相同配对的问题。
三、性能避坑要点
- 禁止使用
NOT IN子查询关联用户历史配对表做排除,用户看过的配对越多,这类查询的性能会越差,最终出现慢查询 - 不要用全表排序的随机算法,比如
ORDER BY RAND()、ORDER BY NEWID(),这类写法会扫描全表所有数据做排序,数据量上涨后性能会断崖式下跌 - 不需要在应用层加分布式锁或者内存锁防重复,数据库层面的联合唯一索引已经可以100%保证数据一致性,加锁反而会降低吞吐量
内容的提问来源于stack exchange,提问作者Eisa
相关产品推荐
相关产品推荐

