LEFT JOIN在千万级大表上慢的根源是数据膨胀、索引缺失和过滤时机错误;应先用子查询/CTE过滤左表,右表JOIN字段必须建索引,避免SELECT *和无LIMIT,且索引是LEFT JOIN高效执行的前提。

LEFT JOIN在千万级大表上慢,核心不是语法问题,而是数据膨胀 + 索引缺失 + 过滤时机错误——三者叠加直接让查询从毫秒级跳到分钟级。
LEFT JOIN前必须先过滤左表
很多人写成 SELECT * FROM big_table a LEFT JOIN small_table b ON a.id = b.a_id WHERE a.status = 'active',但WHERE是在JOIN之后执行的。这意味着MySQL会先把千万行a全拉出来,再跟b逐行匹配,中间生成巨大临时结果集。
- ✅ 正确做法:用子查询或CTE提前裁剪左表,比如
(SELECT id, name FROM big_table WHERE status = 'active' AND create_time > '2025-01-01') a - ⚠️ 注意:WHERE条件里不能含右表字段(如
b.amount > 100),否则会被MySQL退化为JOIN后过滤 - ? 如果左表过滤后只剩几万行,JOIN开销可能下降90%以上
右表关联字段必须有索引
LEFT JOIN的性能瓶颈常卡在右表扫描上。比如orders LEFT JOIN users ON orders.user_id = users.id,若users.id是主键但orders.user_id没索引,MySQL就得对orders每行都扫一遍users全表(Simple Nested-Loop Join)。
- ✅ 确保ON条件中右表字段建了索引,例如
CREATE INDEX idx_orders_user_id ON orders(user_id) - ✅ 更进一步:如果只查
user.name和order.amount,可建覆盖索引CREATE INDEX idx_orders_user_id_amount ON orders(user_id, amount),避免回表 - ⚠️ 常见坑:只给左表建了索引,忘了右表关联列;或者索引顺序反了(如建了
(amount, user_id)却用user_id做JOIN)
避免SELECT * 和无限制结果集
千万级LEFT JOIN最危险的组合是SELECT * + 没LIMIT + 一对多关系——比如1个用户对应500条订单,1万用户就炸出500万行,网络传输、内存排序、客户端渲染全崩。
- ✅ 明确只取需要字段:
SELECT u.name, o.order_no, o.total,别偷懒写* - ✅ 后台分页场景下,强制加
LIMIT 100;如果是导出需求,分批用WHERE id > ?游标方式拉取 - ⚠️ 千万别信“我只看前10条”,MySQL仍会算出全部匹配结果再截断——
LIMIT不减少JOIN计算量
小表驱动大表不适用于LEFT JOIN
INNER JOIN优化常说“小表驱动大表”,但LEFT JOIN里左表是主表,必须全保留,驱动表由MySQL优化器决定,人为指定无效。强行用STRAIGHT_JOIN反而容易让执行计划变差。
- ✅ 关注点应是:左表是否能快速定位(靠WHERE+索引)、右表是否能快速匹配(靠ON字段索引)、连接后是否立即聚合或限制(如
GROUP BY或LIMIT) - ⚠️ 尤其注意NULL值:LEFT JOIN产生大量NULL后,
WHERE b.col IS NOT NULL会把LEFT JOIN变成事实上的INNER JOIN,但执行计划未必能识别,可能仍走低效路径 - ? 真正该调的是buffer pool大小和join_buffer_size,而不是纠结驱动表
最易被忽略的一点:LEFT JOIN的膨胀效应是隐性的。即使你只查10行左表数据,只要右表匹配字段没有索引,MySQL仍可能扫描整个右表——因为它是按左表每行单独去右表找匹配,不是一次性哈希或归并。所以索引不是“可选优化”,是LEFT JOIN在大表上的生存前提。


















