MIN / MAX 是什么
MIN 与 MAX 是取列最值的聚合函数,计算时忽略 NULL,只有在没有任何非 NULL 值时才返回 NULL。它们是最容易走索引优化的聚合:列上有 B 树索引时,MySQL 与 PostgreSQL 都可以直接读索引的一端,拿到值即返回,不必扫描全表——这是 MIN/MAX 与 SUM/AVG 的关键区别。带 WHERE 条件时能否免扫描,取决于索引是否同时覆盖过滤列与最值列。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL / SQL Server / Oracle |
|---|---|
| NULL 处理 | 忽略 NULL 参与计算;所有值都为 NULL 时返回 NULL |
| 索引优化 | 无 WHERE 且列上有索引时可直接读索引端点,MySQL 计划中显示为 Select tables optimized away |
| 返回类型 | 与输入列同类型;字符串按排序规则比较 |
| 与窗口函数区别 | MAX(col) OVER (…) 不折叠行,聚合 MAX(col) 把多行折成一行 |
典型使用场景
- 报表取区间:最早与最晚时间、最低与最高价格
- 找最新一条记录:MAX(created_at) 定位时间边界
- 数据校验:检查数值是否越界、时间是否超出合理区间
- 分组统计:每组的最低最高价,用于定价与异常检测
- 增量抽取:用 MAX(id) 或 MAX(updated_at) 记录上次同步位置
基础用法
基础用法
SELECT MIN(created_at) AS first_order, # 最早一单
MAX(created_at) AS last_order # 最新一单
FROM orders
WHERE user_id = 42; # 复合索引 (user_id, created_at) 时可直接定位
SELECT category, MIN(price) AS low_price, MAX(price) AS high_price
FROM products
GROUP BY category; # 每组的最值
SELECT MAX(amount) FROM orders; # 无 WHERE 时可直接读索引末端同一行同时取最值
同一行同时取最值
-- 一次扫描同时算出 MIN 与 MAX,避免两次全表扫描
SELECT MIN(amount) AS low, MAX(amount) AS high, COUNT(*) AS cnt
FROM orders
WHERE created_at >= '2026-01-01';
-- 需要「最值所在的那一行」时,MIN/MAX 不够,要用排序或窗口函数
SELECT id, amount FROM orders ORDER BY amount DESC, id DESC LIMIT 1;
SELECT id, amount FROM (
SELECT id, amount, ROW_NUMBER() OVER (ORDER BY amount DESC, id DESC) AS rn
FROM orders
) t WHERE rn = 1;计划信号读法
| 计划提示 | 含义 | 处理方向 |
|---|---|---|
| Extra 显示 Select tables optimized away | 直接读索引求最值 | 正常,无需优化 |
| type 为 ALL 且 rows 很大 | 退化为全表扫描 | 检查是否有匹配的复合索引 |
| MIN/MAX 返回 NULL | 没有匹配行,或该列全为 NULL | 业务上区分「无数据」与「值为空」 |
| GROUP BY 后每组最值很慢 | 需要逐组定位 | 考虑窗口函数或预聚合汇总表 |
聚合函数忽略 NULL,这一点与 COUNT 系列不同:列里混有 NULL 时 MIN/MAX 结果不受影响,但 COUNT(col) 会少算这些行,做口径对比时要一起对齐。
用 MAX(id) 当「最新一条」只适用于 id 单调递增、没有历史数据回填的场景;按时间取最新应用 MAX(created_at),并注意同一时间存在多条记录时还需要次级排序键。