社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

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

芋道源码 • 3 周前 • 107 次点击  

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

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

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

国产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/199660