在MySQL日常开发中,JOIN关联查询是高频操作,但相同的业务需求,不同的关联方式可能导致数倍的性能差异。其核心症结在于Join算法的选择 与执行计划的优化。本文将系统拆解MySQL中5种核心关联查询算法,结合实战案例分析适用场景,并总结可落地的优化策略。
一、关联查询的核心算法总览
MySQL的关联查询本质是"驱动表"与"被驱动表"的匹配过程,不同算法的差异体现在"如何高效匹配两表数据"。先通过一张表快速掌握各算法的核心逻辑:
| Join算法 | 核心原理 | 适用场景 | 关键优势/劣势 |
|---|---|---|---|
| Simple Nested-Loop Join | 驱动表每行→被驱动表全表扫描匹配 | 无(MySQL未实际采用) | 逻辑简单,扫描行数m*n,效率极低 |
| Index Nested-Loop Join | 驱动表每行→通过索引定位被驱动表匹配数据 | 被驱动表关联字段有索引 | 扫描行数少,依赖索引效率 |
| Block Nested-Loop Join | 驱动表数据批量写入join_buffer→被驱动表每行与缓冲区数据对比 |
MySQL 8.0.20前,被驱动表无索引 | 减少全表扫描次数,依赖缓冲区大小 |
| Hash Join | 驱动表构建哈希表→被驱动表逐行通过哈希函数匹配 | MySQL 8.0.20后,被驱动表无索引 | 减少IO,比BNL更省资源 |
| Batched Key Access | 驱动表数据批量入join_buffer→MRR接口排序主键→批量匹配被驱动表索引 |
被驱动表有索引,大数据量关联 | 批量处理+顺序IO,效率最优 |
二、逐个拆解:5种Join算法的原理与实战
2.1 被淘汰的"基础款":Simple Nested-Loop Join

原理
最朴素的关联逻辑:遍历驱动表(数据量m)的每一行,都去被驱动表(数据量n)做全表扫描,满足条件则返回结果。
扫描总行数 = m * n,若两表均为1万行,需扫描1亿次,性能极差。
关键结论
MySQL未实际采用该算法------即使被驱动表无索引,也会用Block Nested-Loop Join或Hash Join优化,此算法仅作为理解其他算法的基础。
2.2 索引依赖型:Index Nested-Loop Join(NLJ)

原理
当被驱动表的关联字段有索引时,MySQL优先选择NLJ,流程如下:
- 选择"小表"作为驱动表(减少外层循环次数);
- 遍历驱动表每行,提取关联字段值;
- 通过关联字段的索引,快速定位被驱动表的匹配行;
- 合并两表结果返回。
实战案例
1. 准备测试数据
sql
-- 创建表t1(1万行)和t2(100行,小表)
use martin;
drop table if exists t1;
CREATE TABLE `t1` (
`id` int NOT NULL auto_increment,
`a` int DEFAULT NULL,
`b` int DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`) -- 关联字段a建索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入1万行数据
drop procedure if exists insert_t1;
delimiter ;;
create procedure insert_t1()
begin
declare i int; set i=1;
while(i<=10000)do
insert into t1(a,b) values(i, i); set i=i+1;
end while;
end;;
delimiter ;
call insert_t1();
-- 复制t1为t2,仅保留100行(小表)
drop table if exists t2;
create table t2 like t1;
insert into t2 select * from t1 limit 100;
2. 执行关联查询并分析计划
sql
explain select * from t1 inner join t2 on t1.a = t2.a;
执行计划关键信息 :

- 驱动表是
t2(小表,explain第一行),被驱动表是t1; - Extra字段无"Using join buffer",说明使用NLJ算法;
- 被驱动表通过
idx_a索引匹配,扫描行数极少。
关键结论
- NLJ的效率核心依赖被驱动表的索引,无索引则无法使用;
- 驱动表选择"小表"可减少外层循环次数,优化器默认会自动选择小表作为驱动表(可通过
straight_join强制指定)。
2.3 无索引方案1:Block Nested-Loop Join(BNL)

原理
当被驱动表无索引且MySQL版本≤8.0.19时,采用BNL算法,核心是"批量匹配减少IO":
- 将驱动表数据批量写入
join_buffer(默认大小256KB,可通过join_buffer_size调整); - 遍历被驱动表每行,与
join_buffer中所有驱动表数据对比; - 满足条件则返回结果。
实战案例
sql
-- 关联字段b无索引(t1、t2的b字段均未建索引)
explain select * from t1 inner join t2 on t1.b = t2.b;
MySQL 5.7执行计划关键信息 :

- Extra字段显示"Using join buffer (Block Nested Loop)",确认使用BNL;
- 扫描行数 = 驱动表行数 + 被驱动表行数(批量匹配减少了全表扫描次数)。
关键结论
- BNL比Simple Nested-Loop Join效率高,但仍需扫描被驱动表全表;
join_buffer_size过小时,驱动表会分批次写入缓冲区,导致被驱动表多次全表扫描,需合理调整。
2.4 无索引方案2:Hash Join(MySQL 8.0.20+)

原理
MySQL 8.0.20起,用Hash Join替代BNL,核心是"哈希表快速匹配":
- 将驱动表数据加载到内存,构建"关联字段→行数据"的哈希表;
- 逐行读取被驱动表,通过哈希函数计算关联字段的哈希值;
- 查找哈希表中匹配的哈希值,对比原始数据后返回结果。
实战对比
同上述BNL案例,在MySQL 8.0.25中执行:
sql
explain select * from t1 inner join t2 on t1.b = t2.b;
执行计划关键信息 :

- Extra字段显示"Using join buffer (hash join)",确认使用Hash Join;
- 无需将被驱动表数据写入磁盘/内存,IO次数比BNL更少,性能提升30%+。
关键结论
- Hash Join是无索引场景下的最优选择,建议将MySQL升级至8.0.20+;
- 若驱动表过大,哈希表会溢出到磁盘,需通过
join_buffer_size确保哈希表在内存中。
2.5 性能天花板:Batched Key Access(BKA)

原理
BKA是NLJ的优化版,结合"批量处理"与"顺序IO",需满足被驱动表有索引,流程如下:
- 驱动表数据批量写入
join_buffer; - 批量将关联字段值发送到MRR(Multi-Range Read)接口;
- MRR按主键排序关联字段对应的主键ID,减少随机IO;
- 按排序后的主键批量读取被驱动表数据,匹配后返回。
如何开启BKA
BKA需手动开启MRR相关参数:
sql
-- 开启MRR和BKA
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
-- 验证BKA是否生效
explain select * from t1 inner join t2 on t1.a = t2.a;
执行计划关键信息 :

- Extra字段显示"Using join buffer (Batched Key Access)",确认BKA生效;
- 批量处理减少索引查询次数,MRR排序减少随机IO,大数据量下比NLJ快2-5倍。
三、关联查询优化:4个核心策略
1. 关联字段必须加索引
这是最核心的优化!将"无索引场景"(BNL/Hash Join)转化为"有索引场景"(NLJ/BKA),性能提升可达10倍以上。
案例对比:
- 无索引(BNL):
select * from t1 join t2 on t1.b=t2.b,耗时0.08秒; - 有索引(NLJ):
select * from t1 join t2 on t1.a=t2.a,耗时0.01秒。
2. 强制选择小表作为驱动表
当优化器选择错误时(如统计信息过时),用straight_join强制指定小表为驱动表:
sql
-- 强制t2(小表)为驱动表
select * from t2 straight_join t1 on t2.a = t1.a;
3. 大数据量用BKA优化
对于百万级以上数据的关联查询,开启BKA可大幅减少IO次数,尤其适合"驱动表大、被驱动表有索引"的场景。
4. 升级MySQL至8.0.20+
用Hash Join替代BNL,无索引场景下性能提升30%+,同时减少资源占用。
四、总结
MySQL关联查询的效率,本质是"算法选择"与"资源利用"的平衡:
- 有索引优先用BKA/NLJ,核心是"索引+小表驱动";
- 无索引优先用Hash Join(8.0.20+),避免BNL的高IO;
- 大数据量必开BKA,通过批量处理和MRR优化IO。
掌握这些算法原理与优化策略,可轻松应对90%以上的MySQL关联查询性能问题。