MySQL的MIN()/MAX()函数在满足三点条件下可走索引:WHERE为等值过滤、目标列为索引最左前缀、无GROUP BY/HAVING;否则常退化为全表扫描,因优化器认为其成本更低。

MySQL 的 MIN() 和 MAX() 走不走索引?看这三点
能走,但只在非常明确的条件下走——不是所有带索引的列都自动生效。核心就三条:WHERE 条件必须是等值过滤(= 或 IN),目标列必须是索引最左前缀,且不能有 GROUP BY 或 HAVING 打乱执行路径。
-
常见错误现象:给
price列加了单列索引,执行SELECT MAX(price) FROM products却没用上索引,EXPLAIN显示type: ALL - 为什么这样设计:MySQL 优化器认为全表扫描比遍历 B+ 树叶子节点更省事——因为单列索引的叶子是有序的,但引擎仍需定位到“最后一个”节点,而全表扫描可能更快(尤其小表)
-
实操建议:强制走索引的可靠方式是加上一个恒真等值条件,比如
WHERE category_id = category_id(需该列有索引),或更稳妥地建联合索引:如果常查MAX(price)且按category_id过滤,就建(category_id, price)
联合索引里 MIN()/MAX() 要怎么排序才有效
顺序决定一切。索引定义是 (a, b, c),那 MIN(b) 在 WHERE a = ? 下才可能走索引;如果只写 WHERE c = ?,整个索引就废了。
-
使用场景:统计某类商品最低售价:
SELECT MIN(price) FROM products WHERE category_id = 5→ 索引应为(category_id, price),而不是(price, category_id) -
参数差异:
MIN()和MAX()对索引方向无要求,B+ 树天然支持双向遍历;但如果你同时要ORDER BY ... LIMIT 1,那就得看索引顺序是否匹配排序方向 -
性能影响:用对了,执行计划里
key_len显示只用了索引前缀,rows接近 1;用错了,rows就是全表行数,还可能触发临时表和文件排序
GROUP BY + MIN()/MAX() 索引为啥经常失效
因为 MySQL 在 8.0 之前对这类聚合的索引优化很保守,即使有合适索引,也倾向用临时表分组再计算极值。
-
常见错误现象:
SELECT category_id, MAX(price) FROM products GROUP BY category_id,哪怕有(category_id, price)索引,EXPLAIN仍显示Using temporary; Using filesort -
实操建议:升级到 MySQL 8.0+,并确认
optimizer_switch中启用了skip_scan(默认开启);或者改写成关联子查询:SELECT DISTINCT p1.category_id, (SELECT MAX(p2.price) FROM products p2 WHERE p2.category_id = p1.category_id),配合(category_id, price)索引可命中 -
兼容性影响:5.7 及更早版本基本无法避免临时表;8.0.13+ 对单列
GROUP BY+ 覆盖索引有 Skip Scan 优化,但多列GROUP BY仍大概率退化
别忽略 NULL 和数据分布对索引选择的影响
哪怕索引结构完全正确,只要字段大量为 NULL,或者极值集中在少数几个值里,优化器也可能放弃走索引。
-
容易踩的坑:建了
(status, created_at)索引,想查MIN(created_at) WHERE status = 'active',结果发现status字段 95% 都是'active',优化器直接判定走索引比全表扫描还慢 -
实操建议:用
SHOW INDEX FROM table_name查Cardinality值,如果status的基数远低于总行数(比如 10 行里只有 2 个不同值),就别指望靠它驱动索引扫描;此时更适合建函数索引(8.0.13+):CREATE INDEX idx_active_min ON products ((IF(status = 'active', created_at, NULL))) -
为什么重要:优化器估算成本时,会把
NULL当作独立值参与基数统计,但实际 B+ 树中NULL的存储和比较逻辑特殊,容易导致估算偏差,进而选错执行计划
MIN/MAX 是否生效,从来不是“有没有索引”的问题,而是“查询条件能不能把索引用成一条线性扫描路径”的问题。最容易被忽略的是:你以为的等值条件,在隐式类型转换或字符集不一致时,早就悄悄让索引失效了。










