前言

2017年,《阿里巴巴Java开发手册》 中一条规定掀起技术圈巨浪:“禁止超过三张表进行join操作”

时至今日,这条规范仍被众多企业奉为圭臬。

但背后原因你真的懂吗?

本文将从架构设计、执行原理、实战案例三方面深度解析,带你揭开这条军规背后的技术真相!

希望对你会有所帮助。

一、多表JOIN的性能噩梦

1.1 真实案例:一次血泪教训

某电商平台订单查询接口,原SQL:

SELECT o.*, u.name, u.phone, p.product_name
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN products p ON o.product_id = p.product_id
JOIN warehouses w ON o.warehouse_id = w.id -- 第四张表!
WHERE o.status = 1;

现象

  • 单次查询耗时800ms+
  • 高峰期数据库CPU飙升至90%
  • 频繁触发慢查询告警

原因:MySQL优化器面对四表JOIN时,错误选择了驱动表顺序,导致全表扫描超百万数据!

二、MySQL的JOIN之殇

2.1 执行引擎的先天缺陷

MySQL仅支持三种JOIN算法:

  1. Simple Nested-Loop Join:暴力双循环,复杂度O(m*n)
  2. Block Nested-Loop Join:批量加载到join_buffer,仍为O(m*n)
  3. Index Nested-Loop Join:依赖索引,最优复杂度O(m*log n)

致命缺陷

  • Hash Join(8.0.18前)
  • Sort-Merge Join
  • 多表关联时优化器极易选错驱动表

2.2 优化器的局限性

当表数量增加时:

  1. 可能的JOIN顺序呈阶乘级增长(4表=24种,5表=120种)
  2. MySQL优化器采用贪心算法而非穷举,易选劣质计划
  3. 统计信息不准时雪上加霜

三、分布式架构的致命一击

3.1 分库分表后的JOIN困境

阿里系业务普遍采用分库分表,此时多表JOIN会:

三大痛点

  1. 跨节点数据关联需业务层实现
  2. 网络传输成为性能瓶颈
  3. 事务一致性难以保障

3.2 分库分表后的性能对比

实测数据(订单表分16个库,每库64张表):

查询类型 响应时间 CPU消耗 网络流量
单分片查询 25ms 5% 5KB
跨分片JOIN 1200ms 85% 120MB
内存合并 800ms 70% 80MB

四、破局之道:阿里推荐解决方案

4.1 方案一:分步查询+内存计算

// 1. 查询订单基础信息
List<Order> orders = orderDao.query("SELECT * FROM orders WHERE status=1"); // 2. 提取用户ID去重
Set<Long> userIds = orders.stream().map(Order::getUserId).collect(Collectors.toSet()); // 3. 批量查询用户信息
Map<Long, User> userMap = userDao.queryByIds(userIds).stream()
.collect(Collectors.toMap(User::getId, Function.identity())); // 4. 内存数据组装
orders.forEach(order -> {
order.setUserName(userMap.get(order.getUserId()).getName());
});

优势

  • 避免复杂JOIN
  • 充分利用缓存机制
  • 易于分页处理

4.2 方案二:反范式设计

场景:订单列表需显示商品名称

优化前

SELECT o.*, p.name
FROM orders o
JOIN products p ON o.product_id = p.id -- 需要JOIN

优化后

CREATE TABLE orders (
id BIGINT,
product_id BIGINT,
product_name VARCHAR(100) -- 冗余商品名称
);

取舍原则

  1. 高频查询字段可冗余
  2. 变更少的字段可冗余
  3. 写QPS低的业务可冗余

4.3 方案三:异步物化视图

-- 创建预计算视图
CREATE MATERIALIZED VIEW order_detail_view
AS
SELECT o.*, u.name, u.phone, p.product_name
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN products p ON o.product_id = p.product_id
WHERE o.status = 1; -- 查询直接访问视图
SELECT * FROM order_detail_view WHERE user_id = 1001;

适用场景

  • 实时性要求不高的报表
  • 聚合查询较多的场景

五、何时能打破禁令?

5.1 场景一:使用TiDB等NewSQL数据库

TiDB的分布式Hash Join实现:

核心优化

  • 多线程并发构建Hash表
  • 智能选择Build端(小表)
  • 内存控制+磁盘Spill能力

5.2 场景二:OLAP分析场景

ClickHouse的JOIN策略:

SELECT
a.*, b.extra_data
FROM big_table a
JOIN small_table b ON a.id = b.id
SETTINGS
join_algorithm = 'hash', -- 指定Hash Join
max_bytes_in_join = '10G' -- 内存控制

适用特征

  • 大数据量低延迟分析
  • 主表远大于维表

六、黄金实践法则

6.1 JOIN优化四原则

  1. 小表驱动大表
-- 反例:大表驱动小表
SELECT * FROM 10m_big_table JOIN 100k_small_table -- 正例:小表驱动大表
SELECT * FROM 100k_small_table JOIN 10m_big_table
  1. 被驱动表必须有索引

    ON条件字段必须有索引(除非维表<100行)
  2. 拒绝3张以上JOIN

    超过时优先考虑业务拆分
  3. 禁止跨DB实例JOIN

6.2 军规适用边界

场景 是否允许JOIN 理由
OLTP高频交易 禁用 响应时间敏感
OLAP分析系统 允许 吞吐量优先
分库分表架构 禁用 跨节点JOIN性能差
小表(<100行)关联 允许 性能损耗可忽略

总结

“禁止三表JOIN”本质是架构思维的转变

  1. 从“数据库是全能选手”到数据库专注存储与事务
  2. 从“SQL解决一切”到业务逻辑分层处理
  3. 从“实时一致性”到最终一致性的设计妥协

正如阿里资深DBA所言:

“当你的系统面临千万级并发时,每个微秒的优化都是在为业务争取生存权。规范不是枷锁,而是前辈用血泪换来的生存指南。”

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

本文收录于我的技术网站:http://www.susan.net.cn

阿里巴巴为什么禁止超过3张表join?的更多相关文章

  1. 超过三张表禁止join

    一. 问题提出 <阿里巴巴JAVA开发手册>里面写超过三张表禁止join,这是为什么? 二.问题分析 对这个结论,你是否有怀疑呢?也不知道是哪位先哲说的不要人云亦云,今天我设计sql,来验 ...

  2. 《阿里巴巴JAVA开发手册》里面写超过三张表禁止join这是为什么?

    分库分页.应用里做join 多表join性能很差 参考: 1.https://www.zhihu.com/question/56236190

  3. 十几张表的join(千万级/百万级表) 7hours-->5mins

    ================START============================== 来了一个mail说是job跑得很慢,调查下原因 先来看下sql: SELECT h.order_ ...

  4. CROSS JOIN连接用于生成两张表的笛卡尔集

    将两张表的情况全部列举出来 结果表: 列= 原表列数相加 行= 原表行数相乘     CROSS JOIN连接用于生成两张表的笛卡尔集. 在sql中cross join的使用: 1.返回的记录数为两个 ...

  5. sql用逗号连接多张表对应哪个join?

    转自:http://blog.csdn.net/huanghanqian/article/details/52847835 四种join的区别已老生常谈: INNER JOIN(也可简写为JOIN): ...

  6. 转载:sql用逗号连接多张表对应哪个join?

    http://blog.csdn.net/huanghanqian/article/details/52847835 四种join的区别已老生常谈: INNER JOIN(也可简写为JOIN): 如果 ...

  7. Azure SQL Database (21) 将整张表都迁移到Azure Stretch Database里

    <Windows Azure Platform 系列文章目录>  Azure SQL Database (19) Stretch Database 概览      Azure SQL Da ...

  8. update操作多张表

    sql 语句多张表UPDATE用法一.当用一个表中的数据来更新另一个表中的数据,T-SQL提供多种写法(下面列出了二种),但建议用第一种写法,虽然传统,但结构清晰.飞.飞Asp技术乐园并且要注意,当用 ...

  9. 阿里规范不建议多表Join,可这SQL要怎么写?

    阿里开发手册的描述,禁止多表join: 手册上写着[强制],相信很多同学项目里面的代码都不满足这个要求. 但是关键问题是:不用join,这SQL究竟要怎么写?! 分解关联查询 即对每个要关联的表进行单 ...

  10. SQLSERVER中如何快速比较两张表的不一样

    SQLSERVER中如何快速比较两张表的不一样 不知不觉要写2014年的最后一篇博文了~ 一般来说,如何检测两张表的内容是否一致,体现在复制的时候发布端和订阅端的两端的数据上面 我这里罗列了一些如何从 ...

随机推荐

  1. 探秘Transformer系列之(28)--- DeepSeek MLA

    探秘Transformer系列之(28)--- DeepSeek MLA 目录 探秘Transformer系列之(28)--- DeepSeek MLA 0x00 概述 0x01 原理 1.1 问题 ...

  2. 36条技巧优化PHP代码(总结)

    原文:38条技巧优化PHP代码 1.如果一个方法能被静态,那就声明他为静态的,速度可提高1/4; 2.echo的效率高于print,因为echo没有返回值,print返回一个整型; 3.在循环之前设置 ...

  3. sql学习day2——运用case进行有条件的update(续day1)

    1.薪水表,如下所示,要求:为下一年调整工资22000以下的员工涨工资10%,24000以上的员工减少10% 思考:如果先update薪水24000以上的员工,假设某工资为24000,24000*(1 ...

  4. 松灵机器人scout mini 自主导航(5)——采用CMU团队导航策略

    重操旧业,最近实验室又需要测试无人车导航算法,因此又重新启动了松灵机器人scout mini小车 自主导航项目.通过调研,最终选择了前几年比较火的CMU团队的策略(https://www.cmu-ex ...

  5. Socket实践:使用云服务器当代理连接公司内网,简单实现跨局域网调用部署在公司局域网里的api接口

    公司的代码是可以在公网上访问到,但这些代码里用了部署在公司局域网的api.如果有时想在家写代码看看这个wpf软件的运行情况,就运行不起来,因为主要业务都得连接公司局域网的api接口.我就想用自己的阿里 ...

  6. 【经验】VMware|windows更新20H2版本后VMware虚拟机无法开启(禁用Device guard)

    2021/04/27 针对 Windows 10 的功能更新, 版本 20H2. 出现如下报错. 解决方法参考官网:MSDN-<Manage Windows Defender Credentia ...

  7. storageclass和本地持久化存储

    StorageClass 之前我们部署了PV 和 PVC 的使用方法,但是前面的 PV 都是静态的,什么意思?就是我要使用的一个 PVC 的话就必须手动去创建一个 PV,我们也说过这种方式在很大程度上 ...

  8. Ubuntu 中通过源码安装 Python3.x 环境

    最近在个人前后端分离项目时候, 后端接口程序 fastapi, 在部署的时候, 需要 Pyhton3.8 以上的环境, 但 ubuntu 默认的是 2.7 于是想通过源码安装的方式进行环境搭建. 下载 ...

  9. 实战研究:提升Web应用的安全性

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  10. 通过JS模板引擎实现动态模块组件(Vite+JS+Handlebars)

    1. 引言 在上一篇文章<实现一个前端动态模块组件(Vite+原生JS)>中,笔者通过原生的JavaScript实现了一个动态的模块组件.但是这个实现并不完善,最大的问题就是功能逻辑并没有 ...