es不支持真正随机排序,random_score会拖慢查询、破坏缓存且结果不可复现;应将随机逻辑移至php层,用es查id总数后客户端采样,再精确查询,确保稳定高效。

ES 本身不支持真正的“随机排序”,强行用 _script 或 random_score 会严重拖慢查询、破坏缓存、且结果不可复现——别这么干。
为什么 ES 的 random_score 不适合线上随机展示
很多人看到 Elasticsearch 文档里有 function_score + random_score 就直接套用,结果线上一压就崩:
- 每次请求生成新随机种子,
filter缓存完全失效,所有 query 都走 full compute - 并发稍高(比如 50 QPS),CPU 直接飙满,集群响应延迟从 20ms 涨到 800ms+
- 同一次搜索刷新页面,结果顺序全变,用户点进详情页再返回,列表“消失”了——体验极差
- 无法分页:用
from/size做随机分页,第二页和第一页大概率重复或漏项
Laravel 中真正可用的随机展示方案:客户端采样 + ES 精确查
核心思路是把“随机”这件事从 ES 搬到 PHP 层,让 ES 只干它最擅长的事:精准匹配 + 高速检索。适用于商品、文章、活动卡片等需“看似随机但稳定可复现”的场景。
- 先用 ES 查出满足条件的全部 ID(或估算总数),例如:
$response = $client->search(['index' => 'products', 'body' => ['query' => ['term' => ['on_sale' => true]]], 'size' => 0, 'track_total_hits' => true]) - 拿到
$total = $response['hits']['total']['value'],用 PHP 生成 N 个不重复随机偏移:$offsets = array_unique(array_map(fn() => random_int(0, $total - 1), range(1, $n * 2))) - 对每个 offset 发起一次
scroll或search_after查询(推荐后者),或更简单:用from+size=1批量查(注意from + size ≤ 10000) - 最终合并、去重、取前 N 条 —— 这些操作都在 PHP 内存里完成,不增加 ES 负担
如果非要 ES 层控制随机,只在低频后台用 random_score
仅限管理后台抽样审核、测试数据预览等 QPS < 1、无并发、不关心缓存的场景。必须加严格保护:
- 务必限制
seed:固定 seed(如今天日期)才能保证同一天内结果一致,避免刷新乱序:'random_score' => ['seed' => (int)date('Ymd')] - 必须包在
filter上层,不能放进must:否则影响相关度打分,导致关键词搜不到结果 - 禁用分页:不要配
from和size,直接取size=20,后续翻页改用 scroll ID - 加上超时:
'timeout' => '1s',防止单次查询卡死整个 shard
真正稳定的随机展示,从来不是靠数据库或搜索引擎“打乱顺序”,而是靠客户端可控采样 + 服务端精确命中。ES 的强项是快和准,不是随机;把随机逻辑拉到 Laravel 这边,既省资源,又稳又可测。











