You need to enable JavaScript to run this app. Laravel测试答题模块whereNotIn入参过万是否会拖慢随机抽题查询?
优惠活动
大模型
产品
解决方案
定价
更多

Laravel测试答题模块whereNotIn入参过万是否会拖慢随机抽题查询?

现有实现的性能问题判定

万级排除量下的延迟来源

当单用户已完成10000道答对的题目时,原代码的执行链路会出现三类明显的性能损耗,必然产生可感知的查询延迟:

  • 第一类是PHP层的冗余数据传输:第一个查询$test_status= DB::table('test_status')->where('status', '=', 'check')->select('id')->get();会把10000条已答对的题目ID全部从数据库拉取到PHP内存,光是网络传输、数组生成就会产生固定的内存和时间开销,随着答题数量增长,这部分开销会线性上升。
  • 第二类是超长SQL的执行开销:拉取到PHP的ID数组会被Laravel自动拼接进whereNotIn的条件里,10000个ID会生成超过10KB长度的SQL语句,数据库需要额外耗费时间做语法解析、执行计划校验;且NOT IN条件里的排除值达到千级以上时,数据库优化器很容易选错索引,甚至触发全表扫描。
  • 第三类是随机排序的固有性能缺陷:搭配的inRandomOrder()本质是生成ORDER BY RAND() LIMIT 1的SQL,会强制数据库把所有满足排除条件的行全部取出,逐行生成随机值后做全量排序,完全无法利用主键索引的快速定位能力,是大表随机抽取场景下的典型性能重灾区。

可落地的优化实现方案

第一步:将双查询合并为单条关联查询

彻底去掉PHP层拉取全量排除ID的环节,把排除逻辑下沉到数据库端,通过左关联+空值判断的方式实现题目排除,减少跨层数据传输的开销:

$availableCondition = function ($query) {
    $query->from('test_status')
        ->where('status', '=', 'check')
        ->select('id');
};

// 查询剩余可抽题目总数,方便后续做随机偏移计算
$availableCount = DB::table('test')
    ->leftJoinSub($availableCondition, 'done_test', 'test.id', '=', 'done_test.id')
    ->whereNull('done_test.id')
    ->count();

需要配套给test_status表的(status, id)字段建联合覆盖索引,让关联查询完全走索引扫描,不需要回表读取数据,性能会比原写法提升一个数量级。

第二步:替换低效率的全表随机排序

用「随机偏移定位」的逻辑替换原有的ORDER BY RAND(),完全避开全量排序的开销:

if ($availableCount <= 0) {
    // 无可用题目时的分支处理
    return null;
}
// 生成0到剩余题目总数-1的随机偏移量
$randomOffset = random_int(0, $availableCount - 1);

$test = DB::table('test')
    ->leftJoinSub($availableCondition, 'done_test', 'test.id', '=', 'done_test.id')
    ->whereNull('done_test.id')
    ->offset($randomOffset)
    ->limit(1)
    ->first();

避坑提示
  • 不要为了图方便继续使用“拉取全量排除ID到PHP再做NOT IN”的写法,哪怕后续给id字段加了索引,长SQL的解析、网络传输开销也会随排除ID的数量增长快速上涨。
  • 当答题模块的总题量达到十万级、单用户的排除题目量达到万级时,除了优化查询逻辑,还可以定期对已经完全答对、不会再出现的题目做历史数据归档,减少实时查询的数据集规模。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.31 06:15:05