MySQL 5.7 JOIN性能瓶颈90%源于被驱动表无索引、驱动表结果集过大或JOIN字段类型不一致;必须优先解决这三点,否则其他优化基本无效。

MySQL 5.7 的 JOIN 性能瓶颈,90% 都卡在被驱动表没索引、驱动表结果集太大、或两表 JOIN 字段类型不一致这三点上。其他所有“调顺序”“改写法”“加 hint”的操作,如果没先解决这三件事,基本白忙。
被驱动表的 ON 字段必须有索引
这是最常被跳过的硬性前提。哪怕你写了 ON a.id = b.user_id,只要 b.user_id 没建索引,MySQL 就只能对 b 表全表扫描(type=ALL)。更隐蔽的是类型不匹配:比如 a.id 是 INT,b.user_id 是 VARCHAR,隐式转换会让索引直接失效。
- 用
SHOW CREATE TABLE确认两表关联字段的类型、长度、是否允许NULL,必须严格一致 - 索引要显式创建:
ALTER TABLE b ADD INDEX idx_user_id (user_id);外键 ≠ 索引 - 联合索引不能靠“最左前缀”蒙混——如果
ON只用到user_id,它必须是联合索引的第一列,否则无效
驱动表结果集必须足够小
优化器会估算每张表过滤后的行数,并选预估成本最低的顺序。但它的估算依赖统计信息,且容易误判。你不能只靠“左表就是驱动表”这种经验——LEFT JOIN 时,优化器发现右表加了强 WHERE 条件,可能把右表当驱动表,甚至转成 INNER JOIN,导致执行计划崩坏。
- 把高选择性条件(如主键等值、状态+时间范围)尽量写在驱动表上,例如
WHERE u.status = 1 AND u.created_at > '2024-01-01' - 给驱动表建覆盖索引,让
WHERE + JOIN条件都能走索引,比如(status, created_at, id) - 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)或EXPLAIN FORMAT=JSON看真实驱动表是谁、预估行数是否合理;别信默认EXPLAIN输出的表顺序
UPDATE JOIN 比 SELECT JOIN 更容易索引失效
UPDATE 的执行计划比 SELECT 更保守。即使 ON 字段有索引,优化器也可能因 WHERE 条件弱、驱动表预估行数大,直接放弃该索引,转而对右表全表扫描——这在慢日志里表现为锁等待飙升、执行时间超长。
- UPDATE 前必须用等价
SELECT跑EXPLAIN模拟执行计划,比如把UPDATE t1 JOIN t2 ON ...改成SELECT * FROM t1 JOIN t2 ON ... - 复合索引顺序必须匹配实际使用逻辑:等值条件 → 范围条件 → JOIN 字段。例如
WHERE u.is_vip = 1 AND u.level > 5且JOIN ON u.id = o.user_id,索引应为(is_vip, level, id) - 避免在
ON里用函数:ON UPPER(a.name) = UPPER(b.name)会让两边索引都失效
三表以上 JOIN 应优先拆解,而非硬扛
MySQL 5.7 对多表 JOIN 的优化能力有限,尤其当涉及聚合、子查询或非等值条件时,优化器极易选错路径。阿里开发规范禁止三表以上 JOIN 不是拍脑袋——它本质是规避执行计划不可控风险。
- 把五表 JOIN 拆成两个双表查询,在应用层组装结果,降低单条 SQL 复杂度,也避免
join_buffer_size溢出 - 若某中间表数据量大但实际只用子集(如 100 万行中仅需 1000 行),先
CREATE TEMPORARY TABLE存过滤后结果,并立刻给关联字段建索引 - 临时表字段类型尽量窄:用
TINYINT别用INT,百万行省下几十 MB 内存,直接影响缓冲区效率
真正卡住 JOIN 的从来不是语法怎么写,而是哪张表能最快被定位、哪个字段能真正走索引。所有技巧都建立在这两个事实之上:索引存在且被用上,驱动表结果集可控。漏掉任意一个,其他操作只是给慢查询涂口红。



















