在关系型数据库中,Join的实质是按照一定的关联条件,将多个表中的数据逻辑关联起来。这个操作通常面临几个关键难点:
这是最原始的Join实现方式,核心思路是外层表一条条取出数据,与内层表逐条比较。
执行逻辑如下:
for row_out in outer_table:
for row_in in inner_table:
if row_out.key == row_in.key:
output(row_out, row_in)流程图示意:
[外表] → 每行取出
↓
[内表] → 全表遍历或借助索引定位这种变体通过对内表使用索引进行定位,大幅提升连接效率。
执行策略:
执行计划示例:
+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+
| 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 | |
+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+优点:
缺点:
该方法通过将外表数据批量加载到缓冲区中,减少内表的读取次数,从而优化性能。
代码逻辑:
buffer = []
for row in outer_table:
buffer.append(row)
if buffer满了:
for inner_row in inner_table:
for b_row in buffer:
if b_row.key == inner_row.key:
output(b_row, inner_row)
buffer.clear()内存示意图:
+----------------------+
| Join Buffer |
|----------------------|
| 外表记录1 |
| 外表记录2 |
| ... |
| 外表记录N |
+----------------------+join_buffer_size:决定一次能缓存多少外表数据;optimizer_switch:控制是否开启BNL算法。假设外表有M行,内存缓冲可存放B行,内表总页数为N:
总I/O成本 ≈ ⌈M / B⌉ × N
例如:
M = 1,000,000,B = 1,000 → 只需1,000次内表遍历,而不是百万次。
优点:
缺点:
该算法适用于等值连接,通过哈希表加快匹配速度,分为两阶段:
# 构建哈希表(Build Phase)
hash_table = {}
for row in build_table:
k = hash(row.key)
hash_table.setdefault(k, []).append(row)
# 连接探测(Probe Phase)
for row in probe_table:
k = hash(row.key)
if k in hash_table:
for match_row in hash_table[k]:
if match_row.key == row.key:
output(row, match_row)哈希结构示意:
+---------+-------------------+
| Hash键 | 对应记录链表 |
+---------+-------------------+
| 0x1A2F | → row1 → row87 |
| 0x3B7D | → row5 |
+---------+-------------------++----+-------------+-------+------+---------------+------+---------+------+------+----------+------------------------------+
| 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)|
+----+-------------+-------+------+---------------+------+---------+------+------+----------+------------------------------+优点:
缺点:
特性 | Nested-Loop Join | Block Nested-Loop Join | Hash Join |
|---|---|---|---|
支持连接类型 | 所有类型 | 所有类型 | 仅等值连接 |
是否依赖索引 | 是 | 否 | 否 |
内存占用 | 最低 | 中等 | 较高 |
最优使用场景 | 小数据集 + 索引 | 中小数据集 + 无索引 | 大数据量等值连接 |
时间复杂度 | O(MN) 或 O(MlogN) | O(MN/B) | O(M+N) |
磁盘I/O行为 | 随机访问(索引) | 顺序访问 | 内存哈希+顺序扫描 |
支持版本 | 所有版本 | 所有版本 | MySQL 8.0及以上版本 |
开始
↓
是否为等值连接?
├── 是 → 是否内存充足?
│ ├── 是 → 使用 Hash Join
│ └── 否 → 是否有内表索引?
│ ├── 是 → Index Nested-Loop
│ └── 否 → Block Nested-Loop
└── 否 → 使用 Nested-Loop问题:执行计划未显示“Using index”,而是“Using where”。
-- 错误写法(类型不一致)
SELECT * FROM users
JOIN orders ON users.id = orders.user_id
WHERE users.id = '100'; -- users.id是整数优化方式:
ALTER TABLE orders MODIFY user_id INT;
SELECT * FROM users
JOIN orders FORCE INDEX(idx_user_id)
ON users.id = orders.user_id;-- 查看当前缓冲区设置
SHOW VARIABLES LIKE 'join_buffer_size';
-- 临时修改(会话级)
SET SESSION join_buffer_size = 4 * 1024 * 1024;
-- 永久配置
[mysqld]
join_buffer_size = 4MSELECT /*+ HASH_JOIN(t1, t2) */ *
FROM t1 JOIN t2 ON t1.id = t2.t1_id;ref:使用索引连接ALL:全表扫描Using index:命中覆盖索引Using join buffer:BNL或Hash Join已启用{
"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算法的工作机制,可以帮助我们:
建议结合 EXPLAIN ANALYZE 与 Optimizer Trace 进行深度性能分析。