❃博主首页 : 「码到三十五」 ,同名公众号 :「码到三十五」,wx号 : 「liwu0213」
☠博主专栏 : <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关>
♝博主的话 : 搬的每块砖,皆为峰峦之基;公众号搜索「码到三十五」关注这个爱发技术干货的coder,一起筑基

一、Join操作的本质

关系型数据库中,Join操作的本质是通过关联条件将多个表中的数据记录进行逻辑连接。其面临的核心挑战包括:

  • 数据规模问题:当M行外表与N行内表连接时,最坏情况需要M*N次比较
  • 内存限制:无法一次性加载所有数据时的磁盘交换问题
  • 索引利用:如何高效利用数据结构加速查询
  • 连接顺序选择:多表连接时的路径优化

二、MySQL Join算法解析

1. Nested-Loop Join(嵌套循环连接)

1.1 基础概念

定义:最基础的Join算法,通过双重循环逐行匹配数据

核心原理

for outer_row in outer_table:       # 外层循环遍历驱动表
    for inner_row in inner_table:   # 内层循环遍历被驱动表
        if match(join_condition):
            output_result()

示意图

[外表] --> (逐行读取)
            |
            V
          [内表] --> (全表扫描/索引查找)
1.2 时间复杂度分析
  • 最佳情况(内表有索引):O(M * logN)
  • 最差情况(全表扫描):O(M * N)
1.3 索引优化变种(Index Nested-Loop Join)

工作原理

  1. 外层表(驱动表)执行全表扫描
  2. 对每行数据的连接键值:
    • 在内表的索引树上进行查找(B+Tree)
    • 通过索引指针直接定位数据页

执行计划特征

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+
| id | select_type | table | type | possible_keys | key     | key_len | ref               | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+
| 1  | SIMPLE      | t1    | ALL  | NULL          | NULL    | NULL    | NULL              | 1000 |       |
| 1  | SIMPLE      | t2    | ref  | idx_col       | idx_col | 5       | test.t1.join_col  | 1    |       |
+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+
1.4 优缺点分析

优势

  • 内存消耗低(单行处理)
  • 支持所有连接类型(等值、非等值)
  • 支持索引快速定位

劣势

  • 无索引时性能极差
  • 大数据量时响应时间呈指数级增长

2. Block Nested-Loop Join(块嵌套循环连接)

2.1 基本概念

定义:通过缓冲机制优化磁盘I/O的改进型嵌套循环

核心原理

buffer = []
for outer_row in outer_table:
    buffer.append(outer_row)
    if buffer_full():
        for inner_row in inner_table:
            for buf_row in buffer:
                if match(buf_row, inner_row):
                    output_result()
        buffer.clear()

# 处理剩余数据

内存结构

+----------------------+
| Join Buffer          |
|----------------------|
| outer_row1 (join_key)|
| outer_row2 (join_key)|
| ...                  |
| outer_rowN (join_key)|
+----------------------+
2.2 关键参数
  • join_buffer_size:控制缓冲块大小(默认256KB)
  • optimizer_switch:BNL启用开关
2.3 性能特征
总I/O次数 = ceil(M/B) * N 
其中:
M = 外表行数
B = 缓冲容量(行数)
N = 内表数据页数

示例

  • 当M=1,000,000行,B=1,000行时:
  • 传统NLJ需要1e12次比较
  • BNLJ仅需1,000次内表全扫
2.4 优缺点分析

优势

  • 减少内表扫描次数
  • 有效利用内存资源
  • 不依赖索引

劣势

  • 需要合理设置buffer大小
  • 超大表连接仍存在性能瓶颈
  • 不支持非等值连接优化

3. Hash Join(哈希连接)

3.1 基本概念

定义:基于哈希表实现的高效等值连接算法(MySQL 8.0+)

核心原理

# 构建阶段(Build Phase)
hash_table = {}
for build_row in build_table:
    hash_key = hash(build_row.join_key)
    hash_table.setdefault(hash_key, []).append(build_row)

# 探测阶段(Probe Phase)
for probe_row in probe_table:
    hash_key = hash(probe_row.join_key)
    if hash_key in hash_table:
        for build_row in hash_table[hash_key]:
            if build_row.join_key == probe_row.join_key:
                output_result()

内存结构

+---------+-----------------+
| Hash值  | 行指针链表       |
|---------|-----------------|
| 0x3A7F  | → row5 → row128 |
| 0x8B21  | → row42         |
| ...     | ...             |
+---------+-----------------+
3.2 进阶优化

Grace Hash Join

  1. 当哈希表超过内存容量时:
    • 将数据分区写入磁盘
    • 对每个分区分别执行Hash Join

混合哈希连接

  • 动态调整内存/磁盘使用比例
  • 保留热数据在内存中
3.3 执行计划特征
+----+-------------+-------+------+---------------+------+---------+------+------+----------+----------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                            |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+----------------------------------+
| 1  | SIMPLE      | t1    | ALL  | NULL          | NULL | NULL    | NULL | 1000 | 100.00   | Using where                      |
| 1  | SIMPLE      | t2    | ALL  | NULL          | NULL | NULL    | NULL | 1000 | 100.00   | Using join buffer (hash join)    |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+----------------------------------+
3.4 优缺点分析

优势

  • 等值连接性能卓越
  • 适合内存充足的大表连接
  • 天然抗数据倾斜

劣势

  • 仅支持等值连接
  • 内存消耗较高
  • 构建阶段需要完整扫描

三、算法对比矩阵

特性 Nested-Loop Join Block Nested-Loop Join Hash Join
连接类型支持 所有类型 所有类型 仅等值连接
索引依赖 需要内表索引 不需要 不需要
内存消耗 低(逐行处理) 中等(块缓冲) 高(构建哈希表)
最佳数据规模 中小表(带索引) 中小表(无索引) 大表等值连接
典型时间复杂度 O(MN) → O(MlogN) O(M*N/B) O(M+N)
磁盘I/O特征 随机访问(索引) 顺序扫描 顺序扫描+内存哈希
版本支持 所有版本 所有版本 MySQL 8.0+

四、算法选择决策树

开始
等值连接?
内存是否充足?
Nested-Loop
Hash Join
内表有索引?
Index Nested-Loop
Block Nested-Loop

五、深度优化实践

案例1:索引失效分析

现象:执行计划显示Using where而非Using index

-- 错误示例(类型不一致导致索引失效)
SELECT * FROM users 
JOIN orders ON users.id = orders.user_id 
WHERE users.id = '100'; -- id是整数字段

解决方案

ALTER TABLE orders MODIFY user_id INT;
SELECT * FROM users 
JOIN orders FORCE INDEX(idx_user_id) 
ON users.id = orders.user_id;

案例2:BNLJ参数调优

-- 查看当前配置
SHOW VARIABLES LIKE 'join_buffer_size';

-- 动态调整(会话级)
SET SESSION join_buffer_size = 4 * 1024 * 1024; 

-- 永久配置
[mysqld]
join_buffer_size = 4M

案例3:强制使用Hash Join

/* 8.0+版本可用提示 */
SELECT /*+ HASH_JOIN(t1, t2) */ *
FROM t1 JOIN t2 ON t1.id = t2.t1_id;

六、执行计划深度解读

1. EXPLAIN输出关键指标

  • type列
    • ref:索引查找
    • ALL:全表扫描
  • Extra列
    • Using index:覆盖索引
    • Using join buffer:使用BNLJ或Hash Join

2. JSON格式分析

{
  "query_block": {
    "select_id": 1,
    "nested_loop": [
      {
        "table": {
          "table_name": "employees",
          "access_type": "ALL",
          "rows_examined_per_scan": 1000,
          "filtered": "100.00"
        }
      },
      {
        "table": {
          "table_name": "salaries",
          "access_type": "ref",
          "key": "idx_emp_no",
          "used_join_buffer": "Hash Join"
        }
      }
    ]
  }
}

通过理解各Join算法的实现原理,我们可以:

  1. 更精准地设计表结构和索引
  2. 合理配置服务器参数
  3. 编写高效的SQL查询语句
  4. 快速定位性能瓶颈

建议结合EXPLAIN ANALYZEOptimizer Trace进行实战分析。


关注公众号[码到三十五]获取更多技术干货 !

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐