MySQL不总是使用主键索引,是否使用取决于WHERE条件能否命中索引及优化器成本估算;即使主键存在,若查询字段无索引、存在隐式转换、函数运算、OR条件未覆盖或统计信息过期,EXPLAIN中type仍可能为ALL或index、key为NULL。

EXPLAIN 显示驱动表 type=ALL 或 index,但主键明明存在
MySQL 对驱动表的索引使用逻辑和很多人直觉相反:驱动表是否走主键索引,不取决于“有没有主键”,而取决于查询是否需要全行数据、以及 WHERE 条件能否命中索引。即使 PRIMARY KEY 存在,只要执行计划里没用上,就说明优化器认为没必要——或者根本用不了。
常见错误现象:
-
EXPLAIN中驱动表的type是ALL或index,key列为NULL - 表有
PRIMARY KEY(id),但SELECT *+WHERE name = 'xxx'仍全表扫描 - 驱动表加了
WHERE id = ?却没走const,反而显示range或ref
关键原因:
-
WHERE条件字段没索引,主键再好也救不了:驱动表先被 WHERE 过滤,如果过滤字段(如status、name)没索引,MySQL 就得扫全表找匹配行,主键压根没机会出场 -
SELECT *触发回表,但驱动表本身就是聚簇索引,所谓“回表”不成立;真正影响的是:如果WHERE不能用主键,就只能扫整棵 B+ 树(即type=index),等价于全索引扫描 - 主键是
INT,但WHERE用了VARCHAR字符串比较(比如WHERE id = '123'),触发隐式转换,主键索引失效
LEFT JOIN 中左表是驱动表,为什么还是没走主键?
LEFT JOIN 的左表强制为驱动表,但这只固定了执行顺序,不保证它会用主键索引。是否走主键,完全由左表自身的访问方式决定。
典型场景:
SELECT u.*, o.order_id FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.name LIKE '%john%'-
users是驱动表,但name没索引 → 全表扫描 → 主键索引全程闲置
实操建议:
- 先确认驱动表的
WHERE条件字段是否有索引:SHOW INDEX FROM users看name是否在索引列中 - 如果必须模糊查
name,考虑加前缀索引:ALTER TABLE users ADD INDEX idx_name_8 (name(8)),但注意LIKE '%xxx'依然无法用 - 不要依赖“左表=主键表”的错觉:
EXPLAIN第一行对应的就是驱动表,盯紧它的type和key值 - 若驱动表只有
WHERE id IN (…)且 ID 数量少,type应为const或eq_ref;如果是range,检查IN列表是否过大或含 NULL
INNER JOIN 时优化器选错了驱动表,导致主键被忽略
INNER JOIN 不锁定驱动表顺序,优化器按预估行数选驱动表。如果统计信息不准(比如没跑过 ANALYZE TABLE),它可能把本该走主键快速定位的小结果集表,当成大表跳过索引。
容易踩的坑:
-
orders表 200 万行,加了WHERE create_time > '2026-09-01'后只剩 500 行,但优化器误估为 50 万行,于是放弃用orders.id主键,转而拿users当驱动表去嵌套扫描 -
EXPLAIN显示驱动表rows值远高于实际,就是统计信息过期的明确信号
怎么办:
- 立即执行
ANALYZE TABLE orders更新统计信息 - 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)看真实驱动路径,比传统EXPLAIN更准 - 临时干预:改写为
SELECT STRAIGHT_JOIN … FROM orders … JOIN users …,强制orders为驱动表,此时它的id主键才真正参与查找 - 注意:
STRAIGHT_JOIN是双刃剑——数据分布变化后可能更慢,别长期写死在业务 SQL 里
主键索引存在,但 EXPLAIN 里 key 显示 NULL
key 为 NULL 意味着这一行执行没用任何索引,包括主键。这不是 bug,而是优化器主动绕开了它。
最常被忽略的几个点:
- 查询中对主键字段用了函数或表达式:
WHERE YEAR(id) = 2026、WHERE id + 1 = 100→ 主键索引必然失效 - 主键字段允许
NULL,且数据中 NULL 值占比高,优化器判断选择性差,直接弃用索引 - 使用了
OR条件且两边字段无联合索引:WHERE id = 1 OR status = 'active',即使id有主键,也可能退化为全表扫描 - 表字符集和连接条件中字面量字符集不一致,比如表是
utf8mb4,但写了WHERE id = '123' COLLATE latin1_swedish_ci,引发隐式转换
验证方法:
- 执行
SHOW CREATE TABLE users,确认id定义是否为INT NOT NULL PRIMARY KEY - 检查
EXPLAIN FORMAT=JSON输出里的used_columns和key_parts,看优化器是否尝试过主键 - 用
SELECT COUNT(*) FROM users WHERE id IS NULL查 NULL 比例,超 5% 就值得警惕
复杂点往往藏在类型和字符集的一致性里——看着都是 id,一边是 INT UNSIGNED,一边是 INT SIGNED,主键就等于没建。



















