MySQL 8.0+才支持FIRST_VALUE(),低版本需用ROW_NUMBER()等替代;必须带ORDER BY,推荐联合索引优化性能。

MySQL 8.0+ 才能用 FIRST_VALUE() 做分组取首条
低于 8.0 的 MySQL(比如 5.7)压根不支持窗口函数,FIRST_VALUE() 直接报错 FUNCTION xxx.FIRST_VALUE does not exist。必须确认版本:
SELECT VERSION();返回值至少是
8.0.0 或更高。如果不能升级,得换 ROW_NUMBER() + 子查询或关联自查询的方案。
FIRST_VALUE() 默认取的是逻辑顺序第一,不是插入顺序第一
它严格依赖 ORDER BY 子句定义的排序——没写 ORDER BY 会报语法错误。即使业务上“最早插入”很重要,也得靠某个时间字段(如 created_at)显式排序,不能指望物理存储顺序。
常见误写:
SELECT FIRST_VALUE(id) OVER (PARTITION BY category) FROM orders;❌ 缺少
ORDER BY,MySQL 拒绝执行。
正确写法示例(按时间取每类第一条):
SELECT DISTINCT<br> category,<br> FIRST_VALUE(id) OVER (PARTITION BY category ORDER BY created_at ASC) AS first_id,<br> FIRST_VALUE(amount) OVER (PARTITION BY category ORDER BY created_at ASC) AS first_amount<br>FROM orders;
用 FIRST_VALUE() 取整行数据要小心 NULL 和重复值
FIRST_VALUE() 是标量函数,一次只能返回一个列的值。想拿到“第一条记录的所有字段”,不能直接套用多个 FIRST_VALUE() 然后 DISTINCT——当存在相同 created_at 时,不同列可能来自不同行,造成数据错位。
更稳妥的做法是先用 ROW_NUMBER() 标记序号,再过滤:
WITH ranked AS (<br> SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at ASC) AS rn<br> FROM orders<br>)<br>SELECT id, category, amount, created_at<br>FROM ranked<br>WHERE rn = 1;
如果坚持用 FIRST_VALUE() 拼整行,需确保排序字段组合足够唯一(例如加 id 作为第二排序键),否则结果不可靠。
性能上,FIRST_VALUE() 在大数据量分组时可能比子查询慢
窗口函数需要对每个分区做完整排序,若分组多、每组数据大,且没有合适索引,ORDER BY 成为瓶颈。比如按 user_id 分组查每人首笔订单,INDEX(user_id, created_at) 能显著加速;缺这个联合索引,可能全表扫描+临时文件排序。
实操建议:
- 务必给
PARTITION BY和ORDER BY涉及的字段建联合索引 - 避免在
FIRST_VALUE()中调用复杂表达式(如DATE(created_at))作为排序依据,否则无法走索引 - 如果只查少量分组(如固定几个
category),用UNION ALL+ 单个LIMIT 1可能更快
窗口函数简洁,但不是银弹。真实场景里,数据分布、索引覆盖、是否需要整行,这几个点卡住了就该换策略。


















