MIN()/MAX()无索引时只能全表扫描,因其需遍历全部数据确认极值;走索引须同时满足:WHERE为等值过滤、目标列为索引最左前缀、无GROUP BY/HAVING。

MIN()/MAX() 为什么没索引就只能全表扫描
因为 MIN 和 MAX 不是“算完一行就扔”,而是要确认整个数据集的极值——没索引时,MySQL 没法跳到“第一个”或“最后一个”记录,只能逐行比对。B+ 树索引天然有序,但优化器不会主动去叶子节点头尾取值,除非明确告诉它“这条路可行”。
什么情况下 MIN(col) / MAX(col) 才会走索引
必须同时满足三个硬性条件,缺一不可:
-
WHERE条件是等值过滤(=或IN),不能是范围、函数、IS NULL等; -
col是某个索引的最左前缀列(比如索引是(category_id, price),那MAX(price)不行,但MAX(category_id)可以); - 查询不能带
GROUP BY、HAVING或非恒真ORDER BY——这些会打断极值提取路径。
常见错误:给 price 单独建了索引,执行 SELECT MAX(price) FROM products 仍显示 type=ALL。这不是 bug,是优化器权衡后认为扫全表比遍历索引叶子更省事(尤其小表)。
如何让 MIN/MAX 强制走索引
别依赖 FORCE INDEX,它不解决根本问题。可靠做法只有两个:
- 加一个恒真等值条件,且该列有索引:比如
WHERE category_id = category_id(需category_id列有索引),MySQL 8.0+ 会识别为可利用索引边界; - 建联合索引覆盖过滤 + 极值场景,例如常查 “某类商品最高价”,就建
INDEX (category_id, price),再写SELECT MAX(price) FROM products WHERE category_id = 123。
注意:SELECT MAX(price) FROM products WHERE category_id IN (123, 456) 在 MySQL 8.0.22+ 才支持 loose index scan,旧版本仍可能退化为全表扫描。
为什么 COUNT(*) 能走索引,而 MIN/MAX 常常不能
本质区别在于语义和实现路径:COUNT(*) 只关心行数,InnoDB 可直接读取索引的元信息(如聚簇索引的记录数);而 MIN()/MAX() 必须确认真实数据值,且需保证事务可见性——它得沿着索引结构找到第一个/最后一个**当前事务可见**的非删除记录,这个过程无法绕过行级判断。所以即使索引存在,只要 WHERE 条件或列位置不满足前述三点,就大概率 fallback 到全表扫描。
真正容易被忽略的是:哪怕你只查单个字段的极值,只要没配上合适的等值过滤条件,索引就形同虚设。不是建了索引就能用,而是“怎么用”决定了它是否存在。

















