on子句中调用标量函数会导致索引失效、强制全表扫描、性能断崖式下降;因优化器无法将函数结果映射到b+树索引,如on u.email = upper(o.email)使o.email索引完全失效,mysql 5.7不支持函数索引,8.0+虽支持但仅限确定性函数且双侧调用仍难生效,postgresql同理;自定义函数需显式声明确定性级别,否则报错,且执行开销巨大;可行方案是前置计算——业务规范输入、生成列+索引、函数索引(mysql 8.0+)、子查询预计算,但双侧函数调用仍大概率导致优化器弃用函数索引。

ON子句里调用标量函数会直接让索引失效,且强制数据库对每一行都实时计算,性能断崖式下跌——这不是慢一点,而是从毫秒级变成秒级甚至超时。
为什么UPPER()、DATE()这类函数一进ON就崩
数据库优化器无法把函数结果映射到B+树索引上。比如ON u.email = UPPER(o.email),o.email字段即使有索引也完全用不上,因为优化器得先对o表每行算一次UPPER(),再逐个比对,等于主动放弃索引走全表扫描。
- MySQL 5.7不支持函数索引,8.0+虽支持
CREATE INDEX idx ON t ((UPPER(email))),但只限确定性函数,且ON中写成UPPER(u.email) = UPPER(o.email)仍只有一侧能用索引 - PostgreSQL的表达式索引
CREATE INDEX ON t ((LOWER(email)))同理,无法挽救双侧函数调用 - EXPLAIN里一旦出现
type: ALL和Extra: Using join buffer (Block Nested Loop),基本就是函数惹的祸
自定义函数在ON里更危险:确定性声明 + 执行开销双杀
MySQL要求标量函数必须显式声明DETERMINISTIC,PostgreSQL要求STABLE或IMMUTABLE,否则直接报错ERROR 1418或cannot be called in this context。就算过了语法关,性能也扛不住:
- 每匹配一行就调用一次函数,10万行关联时,
my_hash_func(t2.code)可能把响应时间从120ms拉到2.3s - 函数内部若含I/O、会话变量或非确定性调用(如
NOW()),还会导致执行计划不稳定、缓存失效 - LEFT JOIN场景下,右表字段被函数包裹,不仅自身索引失效,还迫使优化器弃用哈希连接,退化为嵌套循环
真正能落地的替代方案:把函数逻辑“前置”
别在ON里现场算,把转换结果提前固化下来,让索引能真正生效:
- 业务层统一规范输入:邮箱存小写,查询也不用
UPPER();时间字段存DATE类型,避免DATE(create_time) - 用生成列+索引:MySQL中
ALTER TABLE users ADD email_lower VARCHAR(255) AS (LOWER(email)) STORED,再对email_lower建索引 - 函数索引兜底:MySQL 8.0+可建
CREATE INDEX idx_t2_hash ON t2 ((my_hash_func(code))),但前提是函数必须DETERMINISTIC - 子查询预计算:把函数逻辑提到JOIN前,例如
(SELECT id, my_hash_func(code) AS h_code FROM t2),再对h_code关联
最常被忽略的一点:即使你加了函数索引,只要ON条件里两侧都用了函数,优化器大概率还是弃用它——它宁可全表扫,也不愿在连接路径里塞两个不确定的计算节点。










