mysql中优化执行计划并减少全表扫描的技巧

来源:这里教程网 时间:2026-02-28 20:50:39 作者:

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

相关推荐