EXPLAIN中key为NULL是COLLATION不一致的铁证,因MySQL优化器在解析JOIN前判定两边collation_name不同致比较不可靠而弃用索引。

EXPLAIN 显示 key 为 NULL 就是 COLLATION 不一致的铁证
不是索引没建,也不是 SQL 写错,而是 MySQL 优化器在解析 JOIN 条件前就判定:两边字段的 collation_name 不同 → 比较结果不可靠 → 主动弃用索引。典型表现:possible_keys 有值但 key 为 NULL,type 是 ALL 或 index,哪怕只关联几万行也秒变全表扫描。
别猜哪边错了,直接查三处:
-
SHOW FULL COLUMNS FROM table1 LIKE 'join_col'—— 看 Collation 列值 -
SHOW FULL COLUMNS FROM table2 LIKE 'join_col'—— 对比是否**一字不差**(例如utf8mb4_0900_as_cs≠utf8mb4_unicode_ci) -
SELECT @@collation_connection—— 连接层决定字面量(如'abc')按什么规则解释
ALTER TABLE MODIFY 必须同时指定 CHARACTER SET 和 COLLATE
只改字符集不改排序规则,等于没改。MySQL 比较时依赖的是 COLLATE,不是 CHARACTER SET 本身。
正确写法(必须两要素齐全):
ALTER TABLE table2 MODIFY join_col VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs;
错误写法(任一缺失都白干):
-
ALTER TABLE table2 CONVERT TO CHARACTER SET utf8mb4—— 只改表默认值,不动已有字段的COLLATE -
ALTER TABLE table2 MODIFY join_col VARCHAR(64) COLLATE utf8mb4_0900_as_cs—— 漏了CHARACTER SET,MySQL 可能沿用旧字符集导致隐式转换
改完立刻执行 SHOW CREATE TABLE table2 验证字段定义里是否同时出现 CHARACTER SET utf8mb4 和 COLLATE utf8mb4_0900_as_cs。
ON 子句里加 COLLATE 或 CONVERT 是掩耳盗铃
这类写法语法能过,但索引照样失效:
-
ON t1.name = t2.name COLLATE utf8mb4_0900_as_cs—— 运行时强制转换,t2.name上的索引无法命中 -
ON t1.name = CONVERT(t2.name USING utf8mb4)—— 同样是表达式计算,优化器无法下推索引
后果更严重:
- 执行计划退化为
Using join buffer,内存压力陡增,大结果集容易 OOM - ORM 自动生成的 SQL 不可能自动补上这些修饰,每次手写 JOIN 都得重复抄,极易遗漏
- 掩盖结构缺陷,让 DBA 和开发者误以为“问题已解决”,下次出问题更难定位
连接层 collation_connection 不统一,改表也白搭
就算两张表字段的 COLLATE 已完全一致,如果应用连上来时 @@collation_connection 是 utf8mb4_unicode_ci 或 latin1_swedish_ci,SQL 中的字符串字面量(比如 WHERE name = '张三')仍会被按错误规则解释,JOIN 条件在解析前就失真。
验证和修复方式:
- Java JDBC 连接串加:
?useUnicode=true&characterEncoding=utf8mb4&collationConnection=utf8mb4_0900_as_cs - PHP mysqli 连接后立刻执行:
mysqli_set_charset($conn, 'utf8mb4'),再确认SET NAMES utf8mb4 COLLATE utf8mb4_0900_as_cs - 验证生效:
SELECT CHARSET('测试'), COLLATION('测试')—— 返回值必须与字段Collation完全一致
漏掉 collation= 这部分,前面所有表和字段改得再整齐,查询时仍可能触发隐式转换。


















