mysql自定义函数在where或order by中会导致全表扫描,因其不参与优化器代价估算且无法下推,即使简单函数也会使索引失效,引发cpu空转和type=all执行计划。

自定义函数没走索引,CPU在循环里空转
MySQL自定义函数(CREATE FUNCTION)本身不参与查询优化器的代价估算,即使函数体简单,只要它被用在 WHERE 或 ORDER BY 条件中,就大概率导致全表扫描——尤其当函数包裹了字段(如 WHERE UPPER(name) = 'ABC'),MySQL无法利用 name 上的索引,只能对每行调用一次函数,CPU就在反复计算中拉满。
常见错误现象:EXPLAIN 显示 type=ALL、key=NULL,且 rows 接近表总行数;SHOW PROCESSLIST 里线程状态常为 Sending data 或 Copying to tmp table,但 SQL 看似“很短”。
- 别信函数名“轻量”——哪怕只是
CONCAT(a, b),放在WHERE里也会阻止索引使用 - 函数内含子查询、游标或循环(
WHILE/REPEAT)时,单次调用可能耗 CPU 数十毫秒,QPS 一上去就雪崩 - MySQL 8.0+ 支持函数索引(
CREATE INDEX idx_f ON t ((UPPER(name)))),但仅限确定性函数,且需显式创建,不会自动生效
如何确认是自定义函数在拖垮CPU
不能只看 slow_query_log ——很多自定义函数执行快(long_query_time 默认设为 1 就会漏掉。必须结合 performance_schema 按执行频率和 CPU 时间反向定位。
先查哪些函数被高频调用:
SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT FROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE '%FUNCTION_NAME%' ORDER BY SUM_TIMER_WAIT DESC LIMIT 5;
再确认该 SQL 是否触发了函数内循环:
- 在
information_schema.routines中查函数定义:SELECT ROUTINE_DEFINITION FROM information_schema.routines WHERE ROUTINE_NAME = 'your_func' - 重点搜
WHILE、LOOP、REPEAT、CURSOR关键字——这些是 CPU 密集型信号 - 如果函数调用了其他存储过程或函数,要逐层展开,避免漏掉嵌套调用
替换方案比“优化函数”更有效
自定义函数在 WHERE 条件中本质是反模式。与其花时间优化函数逻辑,不如从调用侧切断依赖:
- 把函数计算提到应用层:比如
UPPER()改成应用里统一转大写后查,数据库只做等值匹配 - 用生成列(
GENERATED COLUMN)+索引替代运行时计算:ALTER TABLE t ADD COLUMN name_upper VARCHAR(64) STORED AS (UPPER(name)), ADD INDEX idx_name_upper (name_upper) - 函数仅用于 SELECT 投影?确保它不参与 JOIN 条件或 WHERE 过滤——否则立刻移出条件表达式
- 实在无法移除,考虑改用 MySQL 内置函数(如
MD5()、JSON_EXTRACT()),它们经过深度优化,且部分支持下推
容易被忽略的陷阱:函数缓存与并发
MySQL 不缓存自定义函数的返回值,每次调用都重新执行。更隐蔽的是:当多个连接同时执行含同一自定义函数的 SQL,函数内部若含临时表、用户变量或非事务性操作(如写文件),可能引发锁竞争或资源争用,表现为 CPU 飙高但 SHOW ENGINE INNODB STATUS 无明显锁等待。
验证方法:
- 用
pt-pmp抓栈:pt-pmp -p $(pidof mysqld) | grep -A5 -B5 your_func_name,看是否大量线程卡在函数入口 - 临时禁用函数测试:
DROP FUNCTION IF EXISTS your_func,观察 CPU 是否回落——注意提前备份定义 - 检查函数是否读写
mysql.proc表或调用SLEEP()类函数,这类操作在高并发下极易放大延迟











