explain中key为null是因join字段collation_name不一致,mysql为保证结果正确性主动弃用索引,强制全表扫描;须通过show full columns、select @@collation_connection及information_schema.columns三处比对并统一collation,修改时必须同时指定character set和collate。

为什么EXPLAIN里key显示NULL,但字段明明有索引
不是索引没建,也不是SQL写错,而是MySQL在JOIN前发现两边字段的collation_name不一致,直接放弃走索引——它宁可全表扫描,也不愿用可能返回错误结果的索引。
根本原因在于B+树依赖严格有序比较。比如utf8mb4_unicode_ci和utf8mb4_0900_as_cs对“café”和“cafe”的等价判定不同,数据库无法保证索引扫描能覆盖所有逻辑相等的记录,所以优化器主动降级为全表扫描或隐式转换。
- 即使
CHARACTER SET都是utf8mb4,只要COLLATE不同(如utf8mb4_general_civsutf8mb4_0900_as_cs),照样失效 -
SHOW CREATE TABLE里看到的COLLATE可能只是表级默认值,实际列级定义可能被覆盖,必须用SHOW FULL COLUMNS确认 - 连接层的
@@collation_connection会影响SQL字面量(如'abc')的默认排序规则,若与字段不一致,单表WHERE也可能失效
怎么快速定位是哪一层collation不匹配
别猜,三步查清:
- 查字段真实定义:
SHOW FULL COLUMNS FROM t1 LIKE 'join_col';,看Collation列 - 查当前会话生效规则:
SELECT @@collation_connection, @@character_set_client; - 查JOIN另一端:
SELECT column_name, character_set_name, collation_name FROM information_schema.COLUMNS WHERE table_name = 't2' AND column_name = 'join_col';
重点对比三者:字段定义、连接会话、另一张表对应字段。只要任一环节不一致,JOIN就大概率失效。
ALTER TABLE改collation时最容易踩的坑
只改CHARACTER SET不改COLLATE,等于白干。MySQL比对的是排序规则,不是字符集本身。
- 错误写法:
ALTER TABLE t1 CONVERT TO CHARACTER SET utf8mb4;→ 保留原COLLATE,可能仍是utf8mb4_unicode_ci - 正确写法:
ALTER TABLE t1 MODIFY join_col VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs; - 大表操作会锁表(除非满足
ALGORITHM=INPLACE条件),务必低峰期执行 - 改完立刻验证:
SHOW CREATE TABLE t1;确认字段定义已更新,再跑EXPLAIN看key是否出现索引名
改完表还不行?检查应用连接层
表字段统一了,但应用连上来时@@collation_connection还是旧的,SQL字面量(比如WHERE join_col = 'xxx')仍按老规则解析,照样不走索引。
- 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完全一致
最麻烦的是它不报错,只悄悄变慢;你得盯着EXPLAIN里的key和type列,而不是看查询有没有报错。











