explain显示type=all需警惕,代表全表扫描,i/o和cpu压力陡增;真正问题常是索引未被使用或统计信息不准,应检查key是否为null、rows是否接近总行数,并优化索引设计与查询写法。

为什么 EXPLAIN 显示 type=ALL 就该警惕
这代表 MySQL 正在做全表扫描,哪怕表只有几万行,一旦并发上来或字段有 TEXT/BLOB,I/O 和 CPU 压力会陡增。真正的问题往往不是“没加索引”,而是索引没被用上——比如查询条件用了函数、隐式类型转换,或 OR 连接了非索引列。
- 检查
EXPLAIN的key字段是否为NULL;若不是NULL但rows接近表总行数,说明索引区分度太低或查询未命中有效前缀 - 避免在索引列上使用
LEFT(col, 5)、DATE(created_at)等函数,改用范围查询(如created_at >= '2024-01-01' AND created_at ) -
WHERE status = 1 OR is_deleted = 0很容易跳过索引,可拆成UNION ALL或改用覆盖索引 + 条件重写
复合索引的列顺序不是随便排的
MySQL 只能高效使用索引的最左前缀。例如 INDEX (a, b, c) 能加速 WHERE a = ?、WHERE a = ? AND b > ?,但对 WHERE b = ? 或 WHERE b = ? AND c = ? 完全无效。
- 把等值查询列放最左(如
user_id = 123),再放范围查询列(如created_at > '2024-01-01'),最后放排序/分组列(如ORDER BY updated_at DESC) - 如果常查
WHERE tenant_id = ? AND deleted = 0 ORDER BY created_at DESC,建索引优先考虑(tenant_id, deleted, created_at),而非反过来 - 不要为每个单列都建独立索引——多个单列索引通常不如一个设计合理的复合索引,且会拖慢写入
SELECT * 是隐性全表扫描加速器
即使走了索引,如果查询返回大量非索引字段,MySQL 仍需回表(type=ref 但 Extra 含 Using where; Using index condition),尤其当 innodb_buffer_pool_size 不足时,磁盘随机读会暴增。
- 只查真正需要的字段,比如用
SELECT id, name, status替代SELECT * - 对高频查询,考虑添加覆盖索引:把
SELECT中所有字段都包含进索引,让 MySQL 直接从索引中取数(Extra出现Using index) - 注意
TEXT/BLOB列无法包含在索引中,若必须返回,至少把它们移到SELECT末尾,避免阻塞前面字段的流式传输
统计信息不准会让优化器主动选错路
MySQL 依赖表的行数、索引基数(Cardinality)估算成本。如果执行过大量 DELETE 或 INSERT ... ON DUPLICATE KEY UPDATE,而没更新统计信息,EXPLAIN 显示走索引,实际运行却全表扫——因为优化器以为索引只覆盖 10 行,结果发现它覆盖了 90% 的数据。
- 手动更新:运行
ANALYZE TABLE orders;(轻量,不锁表) - 查看当前基数:
SHOW INDEX FROM orders;关注Cardinality列是否明显偏离真实分布(比如某状态列只有 3 个值,但显示基数为 50000) - 生产环境避免长期禁用自动统计(
innodb_stats_auto_recalc = OFF),除非你有稳定且可控的数据变更节奏
SELECT table_name, column_name, cardinality FROM information_schema.STATISTICS WHERE table_schema = 'mydb' AND table_name = 'orders' ORDER BY seq_in_index;
真正卡住性能的,常常不是没索引,而是索引建了但用不上、统计信息过期、或者查询本身绕过了优化器的预期路径。盯住 EXPLAIN 的 key、rows、Extra 三个字段,比盲目加索引管用得多。










