join中使用函数会导致索引失效,因字段原始值被改变,优化器无法匹配索引;隐式转换、null值、分区裁剪失效、标量函数调用、非确定性函数及or条件等均会加剧性能问题。

JOIN条件里用了函数,索引直接失效
数据库没法用索引加速 ON UPPER(a.code) = UPPER(b.code) 这类写法——函数改变了字段原始值,优化器无法匹配已有的索引定义,只能退化成全表扫描或全索引扫描。哪怕两边都有 code 字段的索引,也白搭。
- 常见错误包括:
DATE(created_at) = DATE(updated_at)、CONCAT(a.first, a.last) = b.fullname、TRIM(c.name) = d.name - MySQL 5.7+ 对
DATE_SUB(b.dt, INTERVAL 1 DAY)这类右侧常量表达式支持索引下推,但前提是左字段没被函数包裹 - 真要忽略大小写,统一用
COLLATE utf8mb4_0900_as_cs(MySQL)或建函数索引:CREATE INDEX idx_lower_name ON users ((lower(name))),但后者只对固定函数有效
隐式转换让JOIN变成逐行计算
ON a.user_id = b.uid 看似简单,但如果 a.user_id 是 INT、b.uid 是 VARCHAR,MySQL 会把整列 b.uid 转成数字再比对——等于给每一行都做一次类型转换,还可能因字符集不一致(比如 utf8_general_ci vs utf8mb4_0900_ai_ci)触发双重转换。
- 用
SHOW CREATE TABLE检查两边字段类型是否完全一致:包括长度(VARCHAR(20)vsVARCHAR(50))、符号性(SIGNED/UNSIGNED)、字符集和排序规则 - NULL 值在 JOIN 条件中基本无法走索引;业务允许时,用默认值(如
0或'unknown')替代,比留空更可控 - 分区表 JOIN 时,
dt字段若被套上YEAR(dt)或DATE(dt),分区裁剪就失效,可能扫几十个无效分区
标量函数调用引发百万次上下文切换
在 SELECT 或 ON 子句里调用自定义标量函数(如 dbo.CalculateTax(@Amount)),每行都会触发一次上下文切换:SQL 引擎切到函数运行环境,执行完再切回来。百万行就是百万次切换,CPU 利用率飙升,且优化器把它当“黑盒”,无法预估成本,容易选错执行计划。
- 函数内如果还嵌套查询(比如查税率表),会放大 I/O,变成嵌套循环中的嵌套循环
- 非确定性函数(
GETDATE()、NEWID())阻止结果缓存,每次执行都重新算 - 简单逻辑(如
@Amount * 0.1)直接写进 SQL 表达式,别封装成函数;复杂逻辑优先用 CTE 或临时表预计算
OR 条件和复杂表达式让优化器放弃索引
ON b.x = a.x OR b.y = a.y 这种写法,几乎所有数据库都会放弃索引,退化为嵌套循环 + 全表扫描。不是它不想用索引,而是 OR 的语义让优化器无法安全地选择单一路由路径。
- 正确解法是拆成
UNION ALL:两个子查询分别从左表出发,LEFT JOIN b AS b1 ON ...和LEFT JOIN b AS b2 ON ... - 确保
b.x和b.y各自有单列索引,或复合索引以它们为前导列 -
ON a.created_at + INTERVAL '1 day' = b.event_time让优化器无法下推索引;改成ON a.created_at = b.event_time - INTERVAL '1 day'(右侧为常量表达式)更可能命中索引
函数本身不慢,慢的是它让数据库失去索引能力、触发隐式计算、阻断优化器判断——这些才是实际拖垮 JOIN 的关键点。











