explain中key为null时,首要排查字段类型、字符集及collation是否一致,其次检查join的on子句是否对索引列使用函数或表达式,二者均会导致索引失效。

EXPLAIN 里 key 是 NULL,先查字段类型和字符集
只要 EXPLAIN 显示 key 列为 NULL,而你确认关联字段明明建了索引,第一件事不是改 SQL,是比对两端字段定义。常见坑点有三个:
- 字段类型不一致:比如
users.id是INT,orders.user_id是VARCHAR,MySQL 会把INT转成字符串去匹配,users.id上的索引就废了 - 字符集相同但
COLLATION不同:比如一边是utf8mb4_0900_as_cs,另一边是utf8mb4_unicode_ci,哪怕都是VARCHAR,也会触发隐式转换 -
NOT NULL约束不一致:一端允许NULL,另一端不允许,有时会让优化器放弃使用索引
查法很简单:SHOW FULL COLUMNS FROM table_name LIKE 'column_name',重点看 Type、Collation、Null 三列;或者用 SELECT column_name, character_set_name, collation_name, is_nullable FROM information_schema.COLUMNS WHERE table_name IN ('t1', 't2') AND column_name = 'join_key' 一次性对比。
ON 里写了函数或表达式,索引直接作废
JOIN 的 ON 子句里对任意一边的索引字段做计算,该字段的索引就失效——这不是 MySQL 特有,PostgreSQL 和 Oracle 同样如此。典型例子:
-
ON u.email = LOWER(o.contact_email)→u.email索引失效 -
ON DATE(o.created_at) = '2024-01-01'→o.created_at索引失效 -
ON CONCAT(u.first_name, ' ', u.last_name) = o.full_name→u.first_name和u.last_name索引全失效
这类写法没有“绕过”方案,CONVERT 或 COLLATE 放在 ON 里只会让右表字段索引也失效。正确做法是数据写入时就标准化:邮箱存小写、时间范围用闭区间(o.created_at >= '2024-01-01' AND o.created_at )。
type = ALL 或 index,说明驱动表选错了或没索引
EXPLAIN 中 type 列出现 ALL 或 index,意味着这个表被当作了驱动表,且没走有效索引。关键要区分是哪张表出了问题:
- 如果左表(
FROM后第一个表)type = ALL,说明它本身缺少索引,或WHERE条件没命中索引(比如用了!=、LIKE '%abc') - 如果右表(
JOIN后的表)type = ALL,大概率是ON字段没索引,或上面说的类型/字符集不匹配 -
type = index表示扫了整棵索引树,性能接近全表扫描,通常是因为联合索引最左列没出现在WHERE或ON条件里
注意:MySQL 默认按表顺序选择驱动表,但可以用 STRAIGHT_JOIN 强制指定。如果小表在后,大表在前,又没索引,很容易拖垮整个 JOIN。
Extra 出现 Using join buffer,说明右表没走索引
Extra 列里看到 Using join buffer (Block Nested Loop),基本等于宣告右表没走索引。这是 MySQL 在找不到合适索引时的兜底策略:把驱动表结果缓存在内存里,再逐行跟右表做嵌套循环匹配——一旦右表数据量稍大,内存吃紧,查询就卡死或 OOM。
这种情况往往伴随 type = ALL 和 key = NULL,但有时优化器会误判,比如统计信息过期。可运行 ANALYZE TABLE table_name 更新统计信息后再看 EXPLAIN 是否变化。不过更大概率还是字段定义或 ON 写法的问题,别指望 ANALYZE 拯救设计缺陷。
真正麻烦的是那种“看起来能走索引,但实际没生效”的情况——比如字符集只差一个后缀、类型看似一致实则 signed/unsigned 不同、或者 COLLATE 被连接层的 @@collation_connection 暗中覆盖。这些细节不拉出定义逐字比对,光看 SQL 很难发现。











