窗口函数性能远优于自定义函数,因其内核级优化支持一次扫描+排序;而自定义函数默认逐行调用,易引发性能陡降、索引失效及结果不确定性等问题。

窗口函数执行快,自定义函数容易拖慢查询
窗口函数本身是数据库内核级优化的计算路径,一次扫描+一次排序就能完成整列计算;而自定义函数(如 fn_mask_phone())默认逐行调用,数据量一上去,CPU 和函数开销就明显拉高。实测 100 万行数据上,SUM() OVER 耗时 80ms,同逻辑用自定义聚合函数(未声明 DETERMINISTIC)可能飙到 2.3 秒——差了近 30 倍。
常见错误现象:
-
EXPLAIN显示Type: ALL且Extra列出现Using temporary; Using filesort多次 - 查询耗时随行数线性增长变成指数增长
- MySQL 错误日志里频繁出现
Function 'xxx' is not marked as DETERMINISTIC
MySQL 8.0+ 不允许在 OVER() 里直接调用自定义函数
你写不了 ROW_NUMBER() OVER (PARTITION BY fn_normalize_dept(dept_name) ORDER BY salary) 这种语句——MySQL 会直接报错 Invalid use of a window function。窗口函数的 PARTITION BY 和 ORDER BY 必须基于原始字段或表达式,不能套自定义函数。
正确做法是分两步:
- 先用自定义函数处理字段,生成中间列:
SELECT *, fn_normalize_dept(dept_name) AS norm_dept FROM employees - 再在这层结果上套窗口:
ROW_NUMBER() OVER (PARTITION BY norm_dept ORDER BY salary)
注意:如果 fn_normalize_dept() 含 I/O 或正则匹配,这一步本身就可能成为瓶颈。
自定义聚合函数能和窗口函数一起用吗?要看数据库支持
MySQL 8.0 不支持用户定义聚合函数(UDA),所以没法写 my_entropy_agg(event_type) OVER (...)。PostgreSQL 可以,但必须满足三个条件:
- 函数声明为
PARALLEL SAFE - 指定
INITCOND = '0'和FINALFUNC -
OVER()的 frame 必须明确,比如ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
Oracle 要求函数标记为 DETERMINISTIC,否则窗口计算可能跳过缓存,重复执行。
索引失效风险比窗口函数本身更大
窗口函数性能好不好,关键看 PARTITION BY 和 ORDER BY 字段有没有复合索引。但如果你在这些字段上套了自定义函数,比如 ORDER BY DATE(created_at) 或 PARTITION BY UPPER(name),索引就完全失效——哪怕你建了 (created_at) 或 (name) 索引也没用。
真正安全的做法:
- 把清洗逻辑前置:用
ALTER TABLE ... ADD COLUMN norm_date DATE GENERATED ALWAYS AS (DATE(created_at)) STORED,再对norm_date建索引 - 避免在
OVER()子句里做任何函数转换 - 确认自定义函数是否真的必要——多数字符串/日期处理,
LEFT()、COALESCE()、STR_TO_DATE()等内置函数更快更稳
最常被忽略的一点:窗口函数输出的是确定性序列,但自定义函数若依赖会话变量、临时表或随机数,就可能让同一查询每次返回不同结果——这在报表和审计场景里极其危险。











