为什么互联网大厂不推荐使用多表 JOIN?
为什么互联网大厂不推荐使用多表 JOIN?
zhangchangsheng面试考察点
- 架构思维:面试官不仅仅想知道 JOIN 有什么问题,更想考察你是否理解分布式架构下的数据访问模式,能否从系统可扩展性角度分析问题。
- 性能意识:是否理解 JOIN 操作在大数据量场景下的性能瓶颈,能否从索引、锁、内存等角度分析 JOIN 的成本。
- 实践经验:是否了解大厂的分库分表策略,能否给出替代 JOIN 的具体方案。
核心答案
互联网大厂不推荐使用多表 JOIN,核心原因如下:
| 问题维度 | 具体表现 | 影响 |
|---|---|---|
| 性能问题 | 大表 JOIN 消耗大量内存和 CPU | 查询变慢甚至超时 |
| 锁竞争 | JOIN 涉及多表,锁定时间更长 | 并发性能下降 |
| 分库分表 | 跨库 JOIN 无法直接执行 | 架构受限 |
| 可扩展性 | 数据量增长后 JOIN 性能急剧下降 | 系统瓶颈 |
| 索引依赖 | JOIN 依赖关联字段索引 | 索引维护成本高 |
| 代码耦合 | SQL 逻辑复杂,难以维护 | 开发效率低 |
一句话总结:JOIN 在单机小数据量场景可用,但在大数据量、高并发、分布式架构下,会成为性能杀手和架构绊脚石。
深度解析
一、性能问题:大数据量下的 JOIN 是性能杀手
flowchart TB
A["假设:orders 表 1000 万条<br/>users 表 100 万条"] --> B["步骤 1:扫描 orders 表(或走索引)<br/>关联列没索引则全表扫描 1000 万条"]
B --> C["步骤 2:对每条记录去 users 表查找关联用户<br/>约 1000 万次索引查找(假设 user_id 有索引)"]
C --> D["步骤 3:将结果写入临时表<br/>内存不足时溢出到磁盘"]
D --> E["步骤 4:返回最终结果"]
E --> F["风险点:磁盘临时表导致性能急剧下降<br/>扫描行数多,CPU 消耗大<br/>执行时间长,锁持有久,并发下降"]
上图展示了 JOIN 操作的执行过程和潜在风险。具体说明:
- 扫描成本:如果关联字段没有索引,需要进行全表扫描,数据量大时非常耗时。
- 内存消耗:JOIN 需要将中间结果保存在内存中,数据量大时可能触发临时表写入磁盘,导致性能断崖式下降。
- CPU 消耗:大量的比较和匹配操作会消耗大量 CPU 资源,影响数据库整体性能。
MySQL 的 JOIN 实现方式:JOIN 算法 特点 适用场景 Simple Nested-Loop Join 外层表每行都扫描内层表 性能最差,基本不用 Index Nested-Loop Join 内层表使用索引查找 内层表关联字段有索引 Block Nested-Loop Join 外层表数据读入 Join Buffer,批量匹配 内层表无索引(MySQL 8.0.18 前) Hash Join 构建哈希表进行匹配 MySQL 8.0.18+ 新增,无索引场景
二、分库分表:跨库 JOIN 直接废掉
flowchart TB
subgraph S1["单库场景:JOIN 可用"]
A["orders 订单表"] -->|"JOIN"| B["users 用户表"]
end
subgraph S2["分库场景:JOIN 失效"]
C["订单库 DB1<br/>orders_0 / orders_1 / orders_2"] -.->|"无法跨库 JOIN"| D["用户库 DB2<br/>users 用户表"]
end
subgraph S3["解决方案:应用层组装数据"]
E["1. 查询 orders 表"] --> F["2. 提取 user_id 列表"] --> G["3. 根据 user_id<br/>查询 users 表"] --> H["4. 应用层内存组装"]
end
上图对比了单库和分库场景下 JOIN 的可用性。核心问题:
分库后无法跨库 JOIN:数据分布在不同的数据库实例,SQL 无法直接关联查询。
分表后 JOIN 性能差:即使同库,多张分表做 JOIN,需要扫描所有分表,性能急剧下降。
大厂分库分表现状:用户表按
user_id分库订单表按
user_id或order_id分库商品表按
product_id分库
不同维度的分库策略,导致跨库查询成为常态,JOIN 根本无法使用。
三、替代方案:应用层组装数据
// ❌ 不推荐:使用 JOIN 查询 |
应用层组装的优势:
| 维度 | JOIN 方式 | 应用层组装 |
|---|---|---|
| 可扩展性 | 数据库压力集中 | 计算分散到应用节点 |
| 缓存友好 | 无法缓存中间结果 | 可缓存热点数据 |
| 分库分表 | 跨库 JOIN 失效 | 天然支持跨库 |
| 并发控制 | 数据库锁表 | 无锁竞争 |
| 灵活性 | SQL 写死逻辑 | 可动态调整 |
四、数据冗余:空间换时间
flowchart TB
subgraph A["范式设计(需要 JOIN)"]
direction LR
O1["orders 表<br/>id / user_id / amount / status"] -->|"JOIN user_id"| U1["users 表<br/>id / name / phone / address"]
end
subgraph B["反范式设计(冗余字段,避免 JOIN)"]
O2["orders 表<br/>id / user_id / amount / status<br/>user_name(冗余字段)<br/>user_phone(冗余字段)"]
end
A ~~~ B
O2 --> N1["优点:查询无需 JOIN,直接获取"]
O2 --> N2["缺点:用户改名需同步更新订单表"]
上图展示了通过数据冗余来避免 JOIN 的设计方案。核心思路:
- 冗余常用字段:在订单表中冗余
user_name、user_phone等高频查询字段。 - 同步更新机制:用户信息变更时,通过事件或定时任务同步更新订单表的冗余字段。
- 适用场景:读多写少、对实时性要求不高的场景。
五、什么时候可以用 JOIN?
虽然大厂不推荐,但以下场景可以考虑:
| 场景 | 是否可用 JOIN | 原因 |
|---|---|---|
| 小数据量(< 10 万) | ✅ 可以用 | 性能影响小 |
| 单体应用 | ✅ 可以用 | 无分库分表需求 |
| 后台管理系统 | ✅ 可以用 | 并发低、可接受慢查询 |
| 实时性要求高 | ✅ 可以用 | 避免 IQ 冗余数据延迟 |
| 数据分析报表 | ⚠️ 谨慎使用 | 可用 ClickHouse 等分析型数据库 |
原则:JOIN 不是不能用,而是要在合适的场景用。大数据量、高并发、分布式架构下,应该避免使用 JOIN。
面试高频追问
- JOIN 和子查询哪个性能更好?
- 一般来说 JOIN 性能更好,因为 MySQL 优化器对 JOIN 的优化更成熟。但现代 MySQL 版本中,很多子查询会被自动改写为 JOIN。关键是确保关联字段有索引。
- 如何优化必须用 JOIN 的场景?
- 确保关联字段有索引、小表驱动大表、减少 JOIN 的表数量、只查询必要字段、使用覆盖索引避免回表。
- 你们项目中如何处理多表关联查询?
- 可以回答应用层分步查询 + 本地缓存 + 数据冗余的组合方案。
常见面试变体
- “JOIN 有哪几种类型?有什么区别?”
- “MySQL 的 JOIN 是怎么实现的?”
- “分库分表后如何处理关联查询?”
记忆口诀
JOIN 六大坑:性能差、锁表长、分库难、扩展差、索引贵、维护难 替代三件套:应用层组装、批量查询、数据冗余
总结
互联网大厂不推荐使用多表 JOIN,是因为大数据量下 JOIN 会消耗大量数据库资源,影响系统性能和并发能力;更重要的是,分库分表后跨库 JOIN 根本无法执行。替代方案是:应用层分步查询组装数据、批量查询减少交互次数、数据冗余避免频繁关联。JOIN 在小数据量、单体应用、后台管理等场景可以使用,但在高并发、大数据量、分布式架构下应该尽量避免。
声明:本文转载自小哈的博客 《为什么互联网大厂不推荐使用多表 JOIN?》,版权归原文作者所有。转载请保留本声明与原文链接。
评论
匿名评论隐私政策
WalineTwikoo
