为什么 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三个字段,比盲目加索引管用得多。
