min() over() 比子查询快,因其仅需一次排序和一次线性遍历,避免每行重复扫描与计算;子查询会触发n次独立min(),执行计划中dependent subquery及rows线性增长即为典型瓶颈。

MIN() OVER() 为什么比子查询快?核心就一条:不重复建上下文
子查询每行都得重新扫描、过滤、求最小值,MIN() OVER() 只需一次排序 + 一次线性遍历。10 万行数据,子查询可能触发 10 万次独立 MIN() 计算;窗口函数只做 1 次聚合状态维护,内存和 CPU 都省在刀刃上。
常见错误现象:执行计划里反复出现 DEPENDENT SUBQUERY
当你看到 EXPLAIN 输出中 Type 列是 DEPENDENT SUBQUERY,且 rows 列数值随外层行数线性增长,基本可以断定这是性能瓶颈。比如:
- 查每个订单所属用户的最低下单金额,写成
(SELECT MIN(order_amount) FROM orders o2 WHERE o2.user_id = o1.user_id) - 执行计划显示 loops=98765,意味着数据库真会跑 98765 次子查询
- 哪怕
user_id有索引,也无法避免重复构建临时结构和重排
实操建议:怎么写才真正快?
MIN() OVER() 快的前提是写对——不是加了 OVER 就自动优化,漏掉关键项照样慢甚至报错:
- 必须带
PARTITION BY,语义要和子查询的 WHERE 条件严格对齐,比如子查询用WHERE dept_id = main.dept_id,窗口就得写MIN(salary) OVER (PARTITION BY dept_id) - 如果字段含
NULL,PARTITION BY dept_id会把所有NULL归为一组,而子查询里dept_id = NULL永远不成立——得提前用COALESCE(dept_id, -1)对齐 - 想按时间取“每个用户最早下单时间”,不能只写
MIN(created_at) OVER (PARTITION BY user_id),得补ORDER BY created_at吗?不用——MIN()是聚合函数,不依赖顺序;但如果你后续要用LAG()或做累计,那就必须加 - 别在
OVER()里乱加ORDER BY:没业务需求却写MIN(x) OVER (PARTITION BY y ORDER BY z),MySQL 会强制为每个分区单独排序,白耗资源
容易被忽略的细节:MIN() 不等于 LAG(),别混用
MIN() 返回的是整个窗口内的最小值,LAG() 返回的是前一行的原始值——这两个函数解决的问题完全不同,强行替换会导致结果错乱:
- 想查“当前订单金额是否低于该用户历史最低金额”,用
amount ✅ - 想查“当前订单比上一笔少多少”,用
amount - LAG(amount) OVER (PARTITION BY user_id ORDER BY order_time)✅ - 但写成
amount - MIN(amount) OVER (PARTITION BY user_id ORDER BY order_time)❌——这算的是“比历史最低少了多少”,不是“比上一笔少了多少” - 而且加了
ORDER BY后,MIN()在窗口内行为会受框架影响:默认是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,相同order_time的多行会被一起纳入,结果可能不符合预期
真正卡住性能的,往往不是函数本身,而是你没意识到 PARTITION BY 和子查询 WHERE 的语义是否等价、NULL 值怎么处理、以及有没有无谓地引入 ORDER BY。这些细节不调好,MIN() OVER() 也可能比子查询还慢。











