mysql如何避免全表扫描_mysql查询优化思路

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

为什么
EXPLAIN
显示
type=ALL
就该警惕

这代表 MySQL 正在对整张表逐行扫描,尤其在数据量上万后,响应会明显变慢。常见诱因不是没加索引,而是查询条件没命中索引最左前缀、用了函数或隐式类型转换,导致索引失效。

实操建议:

EXPLAIN SELECT ...
key
列是否为
NULL
rows
是否接近表总行数
检查
WHERE
中字段是否在复合索引的最左侧位置,比如索引是
(a, b, c)
,但查询只用了
b = ?
,那就不会走索引
避免在索引列上做运算:
WHERE YEAR(create_time) = 2024
→ 改成
WHERE create_time >= '2024-01-01' AND create_time 
字符串字段用数字查询会触发隐式转换:
WHERE mobile = 13812345678
(mobile 是
VARCHAR
)→ 改成
WHERE mobile = '13812345678'

哪些
ORDER BY
会绕过索引排序

即使

WHERE
走了索引,如果
ORDER BY
字段不在索引中,或顺序/方向不一致,MySQL 仍可能触发
Using filesort
,严重时连带放弃索引范围扫描。

实操建议:

复合索引
(a, b, c)
可支持
ORDER BY a, b
ORDER BY a DESC, b ASC
(注意:MySQL 8.0+ 才支持混合方向;5.7 只支持全 ASC 或全 DESC)
ORDER BY a, c
无法利用
(a, b, c)
索引的排序能力,因为跳过了
b
,中间断层
若业务允许,把
SELECT *
改成只查必要字段,减少排序开销;更彻底的做法是加覆盖索引,例如
INDEX (a, b, status, created_at)
来支撑
WHERE a=? AND b=? ORDER BY created_at DESC

LIMIT
加得再早,也救不了没索引的
OFFSET

SELECT * FROM t ORDER BY id LIMIT 10000, 20
看似只取 20 行,但 MySQL 仍要先定位到第 10001 行——这意味着它得扫描前 10020 行。偏移越大,越接近全表扫描。

实操建议:

用游标替代分页:记录上一页最后的
id
,下一页查
WHERE id > 12345 ORDER BY id LIMIT 20
如必须用
OFFSET
,确保
ORDER BY
字段有索引,且该索引能同时满足
WHERE
条件(否则先排序再过滤,代价更高)
对大表分页,可先用子查询缩小主表扫描范围:
SELECT * FROM t JOIN (SELECT id FROM t WHERE status=1 ORDER BY id LIMIT 10000, 20) AS tmp USING (id)

统计类查询为什么总扫全表

COUNT(*)
在 InnoDB 中不直接存总行数,但也不是每次都全扫——关键看有没有可用的二级索引。如果没有,就只能走聚簇索引(也就是整张表);如果有非空索引(比如
INDEX(status)
),优化器可能选它来遍历,更快。

实操建议:

想加速
COUNT(*)
,建一个不为空的窄索引,例如
INDEX(dummy)
,其中
dummy
是定义为
NOT NULL
的小字段(如
TINYINT
避免用
COUNT(column)
统计非空行,除非真需要判空;
COUNT(*)
COUNT(1)
在语义和性能上等价
实时精确总数本身反模式,高并发场景建议用缓存 + 增量更新,或采样估算(如
SELECT TABLE_ROWS FROM information_schema.TABLES
,但它是估算值)

真正难处理的是那些“看起来能走索引、实际却失效”的组合:函数、OR 条件、

!=
NOT IN
、JSON 字段查询……这些地方最容易漏掉执行计划验证。

相关推荐