关系型数据库中,Join操作的本质是通过关联条件将多个表中的数据记录进行逻辑连接。其面临的核心挑战包括:
定义:最基础的Join算法,通过双重循环逐行匹配数据
核心原理:
for outer_row in outer_table: # 外层循环遍历驱动表
for inner_row in inner_table: # 内层循环遍历被驱动表
if match(join_condition):
output_result()
示意图:
[外表] --> (逐行读取)
|
V
[内表] --> (全表扫描/索引查找)
工作原理:
执行计划特征:
+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+
| 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 | |
+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+
优势:
劣势:
定义:通过缓冲机制优化磁盘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)|
+----------------------+
join_buffer_size
:控制缓冲块大小(默认256KB)optimizer_switch
:BNL启用开关总I/O次数 = ceil(M/B) * N
其中:
M = 外表行数
B = 缓冲容量(行数)
N = 内表数据页数
示例:
优势:
劣势:
定义:基于哈希表实现的高效等值连接算法(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 |
| ... | ... |
+---------+-----------------+
Grace Hash Join:
混合哈希连接:
+----+-------------+-------+------+---------------+------+---------+------+------+----------+----------------------------------+
| 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(M*N/B) | O(M+N) |
磁盘I/O特征 | 随机访问(索引) | 顺序扫描 | 顺序扫描+内存哈希 |
版本支持 | 所有版本 | 所有版本 | MySQL 8.0+ |
现象:执行计划显示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;
-- 查看当前配置
SHOW VARIABLES LIKE 'join_buffer_size';
-- 动态调整(会话级)
SET SESSION join_buffer_size = 4 * 1024 * 1024;
-- 永久配置
[mysqld]
join_buffer_size = 4M
/* 8.0+版本可用提示 */
SELECT /*+ HASH_JOIN(t1, t2) */ *
FROM t1 JOIN t2 ON t1.id = t2.t1_id;
ref
:索引查找ALL
:全表扫描Using index
:覆盖索引Using join buffer
:使用BNLJ或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
进行实战分析。