MySQL Semi Join(半连接)解析

冯旭光 Lv4

​ Semi Join(半连接)是 MySQL 优化子查询执行的核心技术之一,它通过特殊算法只检查存在性而不获取完整的结果集,显著提升了包含子查询的 SQL 性能。下面我将从多个维度深入剖析 Semi Join 的原理与实现。

一、Semi Join 的本质特征

1.存在性检查

  • 只判断外层表行是否在子查询结果集中存在匹配
  • 不关心匹配数量(1次或多次都视为存在)
  • 不获取子查询的任何实际数据

2.结果集特性

  • 结果集仅包含外层表的列
  • 每行外层数据最多出现一次(即使子查询有多次匹配)

3.与常规连接的区别

1
2
3
4
5
-- 常规 INNER JOIN (会返回两表匹配行)
SELECT o.* FROM orders o INNER JOIN customers c ON c.ID = o.CUST_ID;

-- Semi Join(只检查存在性)
SELECT o.* FROM orders o WHERE o.CUST_ID IN (SELECT id FROM customers);

二、Semi Join 的触发场景

1. 语法形式触发

  • IN 子查询:

    1
    SELECT * FROM A WHERE x IN (SELECT y FROM B);
  • EXISTS 子查询:

    1
    SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE B.y = A.x);
  • 某些比较操作:

1
SELECT * FROM A WHERE x > ALL (SELECT y FROM B);

2. 优化器自动转换

MySQL 优化器会将某些子查询自动重写为 Semi Join 执行计划,即使 SQL 语句没有显示使用 IN/EXISTS 语法。

三、Semi Join 的五种执行策略

MySQL 实现了多重 Semi Join 算法,优化器会根据成本选择最优方案:

1. Table Pullout(子查询表提升)

适用场景:子查询只引用一个表且简单

执行过程:

  1. 将子查询表提升到外层查询
  2. 转换为普通 JOIN 操作

示例转换:

1
2
3
4
5
-- 原始查询
SELECT * FROM orders WHERE cust_id IN (SELECT id FROM customers WHERE status='active');

-- 优化后
SELECT o.* FROM orders o JOIN customers c ON c.id = o.cust_id WHERE c.status='active';

2. Duplicate Weedout(重复消除)

适用场景:子查询可能有多次匹配

执行过程:

  1. 先执行子查询获取结果集
  2. 在外层查询中使用临时表记录已匹配的外层行
  3. 过滤掉重复匹配

特定:

  • 需要维护临时表记录匹配状态
  • 适用于子查询结果集较小的情况

3. Loose Scan(松散扫描)

适用场景:子查询列有索引且基数高

执行过程:

  1. 利用索引跳跃扫描
  2. 只检查索引中是否存在匹配值
  3. 不需要读取完整的子查询数据

优势:

  • 极大减少 I/O 操作
  • 适合高选择性列

4. Materialization(物化)

适用场景:子查询复杂或无合适索引

执行过程:

  1. 将子查询结果物化为临时表
  2. 在临时表上建立索引
  3. 执行半连接操作

特点:

物化成本较高

适合子查询执行代价大的情况

5. FirstMatch(首次匹配)

适用场景:子查询可能有多次匹配但只需要确认存在性

执行过程:

  1. 对外层表每行,扫描子查询直到找到第一个匹配
  2. 找到即停止,不继续检查剩余匹配

优势:

  • 平均情况下减少子查询执行次数
  • 适合子查询匹配分布不均匀的情况

四、执行计划解读

在 EXPLAIN 输出中识别 Semi Join:

1. 关键标识:

  • Extra 列显示”Using semi join”
  • 可能伴随其他子策略提示如”Using duplicate weedout”

2.执行计划示例

1
2
3
4
5
6
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | orders| NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using where |
| 1 | SIMPLE | cust | NULL | eq_ref| PRIMARY | PRIMARY| 4 | orders.cust_id | 1 | 100.00 | Using index; Using semi join |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

3. 策略识别

  • “Using semi join”表明使用了半连接优化
  • 结合其他 Extra 信息可判断具体策略(如”Using duplicate weedout”)

五、性能优化建议

1. 索引优化

  • 确保子查询连接列有适当索引
  • 复合索引需考虑列顺序

2. 查询重写

  • 将复杂子查询改写为 JOIN 形式
  • 使用 EXISTS 替代 IN (或反之)看哪种更高效

3. 统计信息更新

1
ANALYZE TABLE table_name;
  • 确保优化器有准确的基数统计

4. 监控与调优

  • 使用 EXPLAIN ANALYZE(MySQL 8.0+) 查看实际执行情况
  • 关注临时表使用情况

六、Semi Join 的局限性与注意事项

1. 不支持的情况

  • 子查询包含 GROUP BY、HAVING 或聚合函数
  • 子查询返回多列(某些情况下)
  • 特定类型的比较操作

2. 可能退化为其他执行计划

  • 当优化器认为其他方案成本更低时
  • 数据分布不符合预期时

3. 版本差异

  • 不同 MySQL 版本支持的 Semi Join 策略可能不同
  • MySQL 8.0 对 Semi Join 有更多优化

​ Semi Join 是 MySQL 查询优化器的重要创新,深入理解其原理和实现有助于编写更高效的 SQL 语句,特别是在处理复杂子查询时。实际应用中应结合 EXPLAIN 分析,选择最合适的查询写法。

  • 标题: MySQL Semi Join(半连接)解析
  • 作者: 冯旭光
  • 创建于 : 2025-07-07 10:04:23
  • 更新于 : 2025-07-09 10:28:20
  • 链接: https://blog.fengxuguang.top/posts/5341e1d9/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论