type = all 表示全表扫描,是性能瓶颈的明确信号,需立即干预;应检查索引是否存在、是否被使用,排查隐式类型转换,并优化 order by + limit 和 count(*) 等典型场景。

EXPLAIN 显示 type = ALL 就得立刻干预
看到 EXPLAIN 结果里 type = ALL,别犹豫,这就是全表扫描的明确信号,也是超时的直接源头。不是“可能慢”,而是“一定在拖垮并发”。尤其当 rows 值接近表总行数(比如 300 万行表扫 298 万行),哪怕单次耗时 200ms,在 QPS 50 的接口里,数据库连接池几秒就打满。
实操建议:
- 先确认该 SQL 是否高频——查慢日志或监控平台,别只看单次执行时间
- 检查
possible_keys是否为空:为空说明没索引可选,优先加索引 - 若
possible_keys有值但key为NULL,说明索引存在但没被用,进入下一步排查
索引建了却不用?先盯住隐式类型转换
这是线上最隐蔽、复现率最高的索引失效原因。比如字段是 VARCHAR,查询却传了数字:WHERE order_no = 12345;或者字段是 INT,参数带了引号:WHERE uid = '5837661'。MySQL 内部会自动给索引列加函数做类型转换,等价于 WHERE CAST(order_no AS SIGNED) = 12345,索引直接失效。
实操建议:
- 用
SHOW CREATE TABLE 表名确认字段真实类型 - 检查应用层传参是否严格匹配——Java 用
String接字符串 ID,Go 用strconv.FormatInt而非直接拼接数字 - 在 WHERE 条件里显式转类型反而更安全,比如
WHERE order_no = CAST(? AS CHAR)(需评估兼容性)
ORDER BY + LIMIT 1 也可能触发全表扫描
典型案例如:SELECT * FROM order_info WHERE uid = 5837661 ORDER BY id ASC LIMIT 1。即使有 idx_uid_stat 索引,优化器仍可能选全表扫描——因为它预估:按 uid 找到所有行后,再排序取第一条,不如直接扫一遍顺便找最小 id 更快。这不是 bug,是成本估算逻辑在大表上的副作用。
实操建议:
- 强制走索引:
SELECT * FROM order_info FORCE INDEX (idx_uid) WHERE uid = 5837661 ORDER BY id ASC LIMIT 1 - 改写为子查询:
SELECT * FROM order_info WHERE id = (SELECT MIN(id) FROM order_info WHERE uid = 5837661),前提是id是主键或有索引 - 补联合索引:
ALTER TABLE order_info ADD INDEX idx_uid_id (uid, id),让排序也落在索引内
大表 COUNT(*) 超时?别硬扫,换思路
SELECT COUNT(*) FROM huge_table 在千万级表上基本必超时。MySQL 不缓存精确总数,每次都是真扫——不是你 SQL 写得差,是机制决定它必须慢。
实操建议:
- 业务能接受近似值?用
SHOW TABLE STATUS LIKE 'huge_table'查Rows字段(误差可能达 40%,但够做分页估算) - 需要精确值且频率不高?加个计数器表,用
INSERT ... ON DUPLICATE KEY UPDATE维护,写多读少场景很稳 - 高频精确统计?把 COUNT 拆到写操作里异步更新,或用 Redis HyperLogLog 做去重计数(如 UV)
真正难的不是加索引,而是识别哪些“看似合理”的 SQL 其实正在 quietly 拖垮整个库——比如那个带 ORDER BY 的单条查询,或者每天凌晨跑一次的 COUNT(*) 任务。它们不报错,只悄悄吃掉你的连接数和响应时间。











