慢因在于某张表被反复全表扫描,需优先定位explain中type=all的表,检查其on字段索引、数据类型一致性、索引真实性、函数滥用等问题,并优化join类型、驱动表顺序及覆盖索引设计。

慢不是因为表多,而是某张表被反复全扫——先盯死 EXPLAIN 里 type=ALL 的那张表,它就是病灶。
EXPLAIN 看到 type=ALL 就停手,别写代码了
这是最直接的信号:被驱动表没走索引,正在挨个比对每一行。哪怕只 JOIN 两张表,只要其中一张 type=ALL,性能就崩了。
- 立刻查这张表的 ON 字段有没有索引,比如
orders.user_id没索引,users.id有主键索引也不顶用 - 确认数据类型一致:
users.id是INT,但orders.user_id是VARCHAR,隐式转换会让索引失效 - 别信“我加了索引”,用
SHOW INDEX FROM orders确认索引真在user_id列上,且Cardinality不是 1 - 如果 ON 条件带函数,比如
ON DATE(o.created_at) = u.register_date,索引直接作废
LEFT JOIN 还是 INNER JOIN?语义错了索引也白搭
LEFT JOIN 强制保留左表所有行,优化器没法提前剪枝。而业务上只要“有订单的用户”,却写了 LEFT JOIN,等于主动喂给数据库一堆 NULL 行去处理。
- WHERE 条件里用了右表字段(如
WHERE o.status = 'paid'),LEFT JOIN 实际等价于 INNER JOIN,但优化器未必识别——显式改成JOIN更安全 - 真需要保留左表全部记录,再检查右表是否真的允许 NULL;如果业务逻辑其实不允许,就该改约束,而不是靠 JOIN 类型硬扛
- EXISTS 替代 LEFT JOIN + IS NOT NULL 判断存在性,例如查“有订单的用户”:
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id),不生成中间行,也不怕 NULL 干扰
10 张表 JOIN 慢,问题不在“多”,而在“顺序”
MySQL 默认 Nested Loop Join,复杂度 ≈ 驱动表行数 × 各被驱动表单次查找成本。驱动表选错,10 万行 × 10 次扫描,秒变秒杀。
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)或看rows列,对比各表预估扫描行数,行数最少、WHERE 过滤最强的表优先当驱动表 - 小表(比如
status_codes只有 5 行)必须放最左,否则会被大表循环扫几十万次 - 实在没法让优化器选对,用
STRAIGHT_JOIN强制顺序,但只在EXPLAIN确认它选错时才加,乱用反而更慢 - 别堆 10 张表一起 JOIN,先把高频过滤的表(如带时间范围、状态条件的)先聚合出 ID 列表,再和其他表关联
覆盖索引不是炫技,是省掉回表的随机 IO
SELECT 字段如果不在索引里,即使 ON 和 WHERE 都走了索引,引擎还得回主键 B+ 树捞整行——一次回表≈一次磁盘寻道,10 万行就是 10 万次。
- 把 SELECT 和 ON/ORDER BY 用到的字段全塞进复合索引,比如查
user_id, order_time, status,就建idx_orders_user_time_status(顺序按等值→范围→排序) - EXPLAIN 输出里
Extra出现Using index才算真正命中覆盖索引;出现Using where; Using index说明部分字段还是回表了 - TEXT/BLOB 字段不能进索引,别试图把
description加进复合索引里凑数 - JOIN 多张表时,每张表的覆盖索引要独立设计,
users(status, id)和orders(user_id, amount, created_at)各管各的
真正卡住的从来不是语法,而是中间结果集失控——几十万行 JOIN 出来再 WHERE,不如先 WHERE 再 JOIN。索引建不对、JOIN 类型用错、驱动表选反,这三件事占了 90% 的慢因。动手前,先看一眼 EXPLAIN 的 type 和 rows,别猜。










