为何带引导的查询仍全表扫描?赛马赔率数据查询疑问
这是个非常常见的大表查询优化疑问,结合你的场景(2000万+行的odds表,已建(raceId, horseId)索引),我来拆解下可能的原因:
1. 你看到的“扫描所有行”可能是索引扫描而非全表扫描
首先得明确两个核心概念的区别:
- 全表扫描(Full Table Scan):读取表的所有数据页,对2000万行的表来说速度极慢;
- 索引扫描(Index Scan):仅读取索引的所有条目,而索引的体积通常远小于原表(只存储索引字段),所以哪怕扫描所有索引条目,速度也会快很多。
你的odds表建了(raceId, horseId)索引,如果Query2只需要获取horseId(或raceId+horseId),数据库会直接用这个索引做覆盖索引扫描——不需要回表读取原表数据,只扫索引就能拿到结果。这就是为什么Query2只需要数秒,哪怕执行计划里显示的扫描行数看起来很大。
2. 索引未包含额外过滤条件(若Query2涉及odds表的时间筛选)
如果你的Query2不仅关联races表的指定日期,还需要过滤odds表中某段时间的赔率记录(比如只取比赛前最后一次赔率),但你的索引里没有包含赔率的时间字段,那数据库只能先通过(raceId, horseId)索引找到所有匹配raceId的条目,再逐一过滤时间条件。这种情况下,执行计划会显示扫描大量索引行,但因为索引本身体积小,整体速度还是远快于全表扫描。
3. 需确认races表的过滤结果是否被有效传递
如果races表的raceDate字段没有建索引,日期过滤可能生成较大的raceId列表,导致odds表的索引扫描范围变大。不过你提到Query2很快返回69个horseId,说明查询优化器已经选择了“先从races表取过滤后的raceId,再关联odds表”的高效执行路径,只是扫描的索引条目数量看起来多而已。
验证与优化建议
- 查看执行计划:用
EXPLAIN分析Query2,重点看:type列是否为range或ref(表示有效利用了索引);key列是否是你建的(raceId, horseId)索引;Extra列是否有Using index(表示覆盖索引扫描,无需回表)。
- 补充索引字段:如果需要过滤
odds表的时间字段,可将索引修改为(raceId, horseId, odds_timestamp),这样就能在索引层面完成时间过滤,进一步减少扫描行数。 - 给
races表的日期字段加索引:确保races表的raceDate字段有索引,让日期过滤更快,传递给odds表的raceId列表更小。
举个符合你场景的Query2示例:
SELECT DISTINCT o.horseId FROM races r JOIN odds o ON r.raceId = o.raceId WHERE r.raceDate = '2024-05-20';
这个查询会先用races表的raceDate索引拿到当天的raceId,再用odds表的(raceId, horseId)索引做覆盖扫描,快速去重得到目标horseId——哪怕扫描的索引条目有几十万,也会因为是索引扫描而快速完成。
内容的提问来源于stack exchange,提问作者Troy Wray

