调大 join_buffer_size 对 Nested Loop Join 性能提升极小,仅在驱动表无索引且非等值连接等罕见场景有效;多数情况下应优先为驱动表连接字段添加索引以启用 INLJ。

调大 join_buffer_size 对 Nested Loop Join 的性能影响非常有限,甚至可能适得其反——它只在“无索引驱动表”且“非等值连接”等极少数场景下起作用,绝大多数情况下优化方向完全错误。
为什么 join_buffer_size 几乎不加速 Nested Loop Join
MySQL 的 Nested Loop Join(NLJ)默认使用“Block Nested-Loop Join”(BNL)算法时,才真正用到 join_buffer。但前提是:驱动表(outer table)的连接字段**没有可用索引**,且连接条件是等值(=)——此时 MySQL 才会把驱动表的一批行装入 join buffer,再批量和被驱动表匹配。
如果驱动表连接字段有索引(比如 WHERE t1.id = t2.t1_id 且 t1.id 是主键),MySQL 直接走 Index Nested-Loop Join(INLJ),根本不用 join_buffer,join_buffer_size 再大也没用。
- BNL 只在驱动表无索引、或连接条件含函数/类型转换导致索引失效时触发
-
join_buffer存的是驱动表的**整行数据(或仅连接列)**,不是被驱动表数据 - Buffer 太大会增加内存压力,还可能触发 swap,反而拖慢查询
- 该 buffer 是 per-connection 分配的,高并发下容易 OOM
怎么确认你的 NLJ 真的在用 join_buffer
执行 EXPLAIN FORMAT=JSON 查看执行计划,重点找 "using_join_buffer": "block nested loop" 字段;同时检查 "key" 是否为 NULL(说明驱动表没走索引)。
也可以开启 optimizer trace:
SET optimizer_trace="enabled=on"; SELECT ... JOIN ...; SELECT * FROM information_schema.OPTIMIZER_TRACE\G
在 trace 输出中搜索 join_buffering 和 use_join_cache,确认是否启用及实际分配大小。
- 如果
EXPLAIN显示type: ref或eq_ref,基本可断定走 INLJ,join_buffer_size无效 - 如果
Extra列含Using where; Using join buffer (Block Nested Loop),才是 BNL 场景 -
SHOW STATUS LIKE 'Select_%_join%'中的Select_full_join计数上升,也暗示 BNL 在频繁发生
比调 join_buffer_size 有效的 3 种做法
真正卡住 NLJ 性能的,90% 是驱动表扫描方式和连接字段索引缺失。优先做这些:
- 给驱动表的 ON / WHERE 条件字段加索引(尤其是外键列),让 MySQL 走 INLJ —— 这比调 buffer 快一个数量级
- 避免在连接字段上写表达式:
ON t1.id + 0 = t2.t1_id会让索引失效,退化为 BNL - 用
STRAIGHT_JOIN强制指定驱动表,防止优化器选错顺序(比如小表被当被驱动表全表扫)
示例:若 t2 是小表但没索引,t1 是大表且 t1.t2_id 有索引,应写成 SELECT ... FROM t2 STRAIGHT_JOIN t1 ON t1.t2_id = t2.id,而非依赖优化器自动选择。
join_buffer_size 的安全调整建议
除非明确观察到 BNL 频繁发生且内存充足,否则不要动这个参数。真要调,也必须配合监控:
- 单次调大不超过 4MB(默认 256KB),超过后收益急剧下降
- 观察
Created_tmp_tables和Threads_created是否异常升高(说明内存压力传导到了线程/临时表) - 线上环境建议设为 512KB~2MB,并限制 max_connections 避免总内存超限
- 注意:该参数不能动态修改全局值(
SET GLOBAL不生效),只对新连接有效
最常被忽略的一点:BNL 的瓶颈往往不在 buffer 大小,而在被驱动表的全表扫描 I/O。这时候加索引或改用覆盖索引,比加内存实在得多。


















