Py学习  »  DATABASE

面试拷打:为什么 MySQL 不推荐 join?答出「性能差」只有 30 分

Java基基 • 1 周前 • 51 次点击  

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入芋道快速开发平台知识星球。下面是星球提供的部分资料: 

👉这是一个或许对你有用的开源项目

国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构

RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、 ERPCRMAI大模型、IoT物联网等功能:

  • 多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 微服务:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本 

这道题面试官真正在筛什么

「为什么 MySQL 不推荐使用 join?」——美团、阿里、字节都问过。

字面看是个性能题,实际筛的是三件事 :

  • 你了解 MySQL 的 join 算法吗  ——嵌套循环 / Block Nested Loop / Hash Join 的差距;
  • 你做过分库分表项目吗 ——禁 join 真正的原因不是性能,是分库分表场景里 join 根本跨不了库;
  • 你知道大厂规约里这条规则的本意吗 ——《阿里巴巴 Java 开发手册》写「超过三个表禁止 join 」,背后是工程纪律不是技术绝对论。

只答「join 慢、单表查询效率高」,30 分;能讲清 MySQL 的 join 算法 + 索引利用差异,60 分;能从分库分表 / 缓存命中率 / 索引重建多个角度展开 + 解释为什么不是「绝对禁止」而是「默认禁止」,90 分。

下面按段位拆。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

L1:30 秒答案过基础线

不是 MySQL 不让你用 join,而是生产里默认拆开查更稳 :

  • 数据量大时 join 性能掉得很快 ——单表过百万级,多表 join 容易走全表扫;
  • 应用层做关联可以用缓存 ——单表查询结果可以缓存到 Redis / Caffeine,join 后的复合结果很难复用;
  • 分库分表后 join 跨不了库 ——这是大厂禁 join 的核心原因;
  • schema 演进时 join SQL 不容易找 ——单表查询语法清晰,join 改动散落在多个文件里。

所以默认推荐 :单表查 → 应用层组装 → 必要时用 IN 替代 JOIN

-- 不推荐
SELECT u.name, o.amount
FROM`user` u
JOIN`order` o ON u.id = o.user_id
WHERE u.status = 1;

-- 推荐
SELECTidnameFROM`user`WHEREstatus = 1;
SELECT user_id, amount FROM`order`WHERE user_id IN (123, ...);
-- 应用层做 join

这一档的回答到这里 30 分 。但面试官会追问「join 内部怎么跑的」「IN 真的比 JOIN 快吗」——要继续往上必须讲算法。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

L2:2 分钟答案显原理

MySQL 的 join 不是单一算法,MySQL 8.0 之前 主要三种:

算法
触发条件
复杂度
现实表现
Index Nested Loop(INL)
关联字段有索引
O(n × log m)
最好的情况,索引能命中
Block Nested Loop(BNL)
关联字段无索引
O(n × m / block_size)
全表扫,性能急剧下降
Simple Nested Loop(SNL)
早期版本,无优化
O(n × m)
实际上 8.0 之前已被 BNL 替代

MySQL 8.0.18 之后 新增了 Hash Join ——关联字段无索引时性能比 BNL 强一个量级。但对应用代码无感,依然是 EXPLAIN 才能看出来。

索引能不能命中是分水岭

SELECT * FROM `user` u JOIN `order` o ON u.id = o.user_id;
  • 如果 order.user_id 上有索引——走 INL,用驱动表的每行去走索引查被驱动表 ,性能很好;
  • 如果没索引——走 BNL(8.0 之前)/ Hash Join(8.0+),扫被驱动表全表。

join 的性能不是「快慢绝对论」,是「索引能不能命中」 ——索引命中时 join 一点都不慢,命中不了哪怕单表都慢。

应用层 IN 不一定比 JOIN 快

很多人以为「拆开 + IN」一定比 JOIN 快——不一定 。IN 的性能取决于:

  • IN 列表的大小(MySQL 内部按数量切换不同执行计划);
  • 单表查询的索引是否合理;
  • 网络往返次数(两次 SQL = 两次 RTT)。

order 表的 user_id 字段如果有索引,那么:

  • INNER JOIN :1 次 SQL,1 次 RTT;
  • 拆开 + IN :2 次 SQL,2 次 RTT。

数据量小的时候 JOIN 反而更快。性能对比要拉 EXPLAIN 看,不要靠直觉 。

缓存命中率才是关键差距

应用层拆开查的真正优势在缓存 :

  • 单表查询结果好缓存——user_by_id_82001 这种 key 随便缓;
  • JOIN 结果难缓存——user_join_order_status_1_amount_gt_100 这种 key 命中率极低;
  • 一张表改变,所有 JOIN 缓存全失效;拆开查只影响该表的缓存。

这是 60 分答案的关键差异点 ——比性能更核心的是缓存策略。

L3:5 分钟答案显大厂经验

大厂规约(阿里、美团、字节都类似)写「超过 N 张表禁止 join 」,N 通常是 3。这条规则的真正原因不是性能,是工程纪律 ——下面讲三个真实驱动因素。

真凶 1:分库分表后 join 物理上跨不过库

这是禁 join 的最核心理由 。

业务规模上来了,user 表分到了 user_db_0 ~ user_db_3,order 表分到了 order_db_0 ~ order_db_3,路由规则按 user_id 取模:

SELECT u.name, o.amount FROM user u JOIN order o 


    
ON u.id = o.user_id;

这条 SQL 在分库分表中间件里根本跑不了 ——MyCAT、ShardingSphere 跨库 join 表现都极差,同库 join 能力勉强,跨库基本只能拉到内存里 join,OOM 风险大。

所以大厂的规约是"为分库分表预留空间" ——单库阶段允许 join 的代码,到分库后要全部重写,与其将来熬夜重写,不如从一开始就拆开查。

国内项目里 MyBatis-Plus 的统治地位很高,MP 的 BaseMapper 默认设计就是单表查询 ——很多团队的 ORM 选型本身就在引导大家「先单表 + 后组装」。

真凶 2:N+1 查询是个坑,但有解

「拆开查」最大的反对意见是 N+1 查询 ——查 100 个用户的订单,先查用户,再循环查订单,1 + N 次 SQL。

// 反面教材:N+1 查询
List users = userMapper.selectList(...);
for (User u : users) {
    List orders = orderMapper.selectByUserId(u.getId());   // ← N 次 SQL
}

但 N+1 是有解的——用 IN 批量查 :

List users = userMapper.selectList(...);
List userIds = users.stream().map(User::getId).toList();
List orders = orderMapper.selectByUserIds(userIds);   // ← 1 次 SQL
Map> grouped = orders.stream()
    .collect(Collectors.groupingBy(Order::getUserId));
// 应用层组装

这是大厂代码的标准姿势 ——不靠 JOIN,靠 IN + 应用层 grouping。

真凶 3:schema 变更影响面控制

JOIN 在多个文件里散落分布——UserService 一处、OrderService 一处、ReportService 一处。改 user 表加一个字段、改字段类型、加索引时,影响面分散在所有 JOIN 处 。

单表查询则集中在 Mapper 层——一张表对应一个 Mapper,改 schema 只改一个 Mapper。

这条规约本质是「降低系统耦合」 ,不是单纯的性能问题。

那什么时候 join 是合理的

按场景:

场景
join 值不值得用
后台管理系统
 (数据量小,无分库分表)
✅ 合理使用 join,简化代码
报表 / 数据分析
 (一次性查询,结果直接展示)
✅ join 比拆开查更直观
C 端业务核心接口
 (高 QPS,未来要分库分表)
❌ 默认拆开,预留分库空间
跨库 / 跨服务的关联
 (微服务)
❌ 跨服务用 RPC + IN,不要直连数据库
批处理 / 离线任务
 (量大但不要求低延迟)
✅ 可以 join,配合索引优化

「不推荐 join」的潜台词不是「禁止」,是「默认拆开 + 例外允许」 ——遇到合理场景该 join 还是要 join,不要一刀切。

直接掉分的几种答法

按扣分严重程度倒序:

  • 「join 性能差,所以不用」 ——只答这条 = 没做过分库分表,30 分封顶;
  • 「IN 一定比 JOIN 快」 ——错。索引命中时 JOIN 反而更快,性能要看 EXPLAIN 不要靠直觉;
  • 「join 会锁表」 ——基本错。InnoDB 的 join 是行级锁不是表锁,跟单表查询锁粒度一致;
  • 「join 全表扫」 ——只对一半。INL 算法走索引,BNL / Hash Join 才扫表;
  • 「拆开查肯定快」 ——也是只对一半。拆开查多了 RTT,量小场景反而更慢;
  • 从来不提分库分表 ——大厂禁 join 的最核心原因没答出来,封顶 60 分;
  • 「join 永远不该用」 ——绝对论。报表、后台、批处理场景 join 是更优解。

高频追问怎么接

Q1:MySQL 8.0 的 Hash Join 是不是把 join 性能问题解决了?

没有 。Hash Join 只是让无索引 join 比 BNL 强一些,但仍然要扫被驱动表全表,仍然不解决跨库问题,仍然不解决缓存命中率问题。应用层拆开的优势依然存在 。

Q2:为什么阿里规约写「超过三张表禁止 join」?

三张表是个经验值——两张表 join 一般还能走 INL ,三张表往上 MySQL 优化器选错驱动表的概率急剧上升,调优成本远超拆开查。

Q3:拆开查的事务怎么处理?

这是拆开查的真正难点 ——单表查询每条独立 SQL,跨表写入没有事务一致性。

通用做法:

  • 读场景:业务里允许短暂不一致;
  • 写场景:用 Spring  @Transactional 包住多个 Mapper 调用(同库),或用消息队列做最终一致性(跨库);
  • 强一致写:单库内还是用 join + 事务,生产里没人为了禁 join 而牺牲事务 。

Q4:ORM 如 MyBatis-Plus 的 BaseMapper 不支持 join,怎么办?

国内项目几乎全用 MyBatis-Plus ——MP 的设计哲学就是单表 + 应用层组装。要 join 用 MP-Join 插件、或者直接写 XML、或者用 JOOQ 这种类型安全的查询构造器。

但更合理的做法是接受 MP 的限制,强制团队走「单表 + IN + 应用层组装」 ——这跟大厂规约是一致的。

Q5:ElasticSearch / ClickHouse 这种场景呢?

ES 不支持 join,从设计上就要把数据拍平存进去(反范式)。ClickHouse 支持 join 但优化器没 MySQL 强。OLAP 场景的标准姿势是「写入时反范式,查询时单表」 ——这跟「拆开查 + 应用层组装」是同一思路。

一句话收口

「为什么 MySQL 不推荐 join」真正考的不是性能 ,是你对 ORM 选型 + 分库分表 + 工程纪律的理解 :

  • 30 分 :知道 join 性能问题、应用层拆开查;
  • 60 分 :能讲清 INL / BNL / Hash Join 的区别 + 缓存命中率差异;
  • 90 分 :能从分库分表、N+1 解法、schema 演进、规约本意四个角度展开。

写 SQL 的核心心法很简单——默认单表 + 应用层组装,必要时回落 join,但要知道这是「逃生通道」不是「默认路径」 。

「不推荐 join」不是技术绝对论,是为系统未来的扩展性预留空间 ——当下 join 能省 5 行代码,未来分库时要重写 500 行——这账,大厂帮你算过了。



欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/195883