Appearance
MySQL 连接详解
很多人在学 MySQL 时,会把连接记成几条语法:
inner joinleft joinright joincross join
但如果只背语法,到了真实业务里很容易混淆:
- 到底什么时候该用内连接,什么时候该用左连接
on和where的过滤到底差在哪- 为什么有些连接会把数据“连丢”
- 为什么有些查询一
join就变慢
这篇文章就专门把 MySQL 里的“连接”讲清楚:它是什么、有哪些方式、彼此有什么区别,以及分别适合什么场景。
1. 什么是连接
连接,也就是 join,本质上是在多张表之间按照某种关联条件,把相关数据拼到一条查询结果里。
你可以把它理解成:单表查询是在一张表里找数据,连接查询是在多张表里把相关数据拼起来再返回。
例如:
- 订单表里有
user_id - 用户表里有
id - 当我们想查“订单是谁下的”时,就需要把订单表和用户表连起来
典型 SQL:
sql
select
o.id as order_id,
o.total_amount,
u.username
from orders o
join user u on o.user_id = u.id;这里的意思就是:
- 以
orders为主 - 去
user表里找id = o.user_id的用户 - 把订单和用户名一起查出来
2. 连接到底在解决什么问题
关系型数据库把数据拆成多张表,本质上是为了:
- 降低冗余
- 保持数据一致性
- 让结构更清晰
但表一拆开,查询时就会遇到一个新问题:一条业务数据,往往分散在多张表里。
比如一个订单详情页,通常需要:
- 订单主信息
- 下单用户信息
- 商品明细信息
- 商品名称和价格信息
这时候如果完全不用连接,就只能:
- 先查订单
- 再查用户
- 再查订单明细
- 再查商品
这样写当然也行,但 SQL 会被拆散,应用层拼装成本更高。
连接的意义就是:让数据库直接帮我们把多张表中相关的数据按条件关联起来。
3. 先准备两张最简单的示例表
为了方便说明,下面主要用这两张表:
sql
create table user (
id bigint primary key,
username varchar(64) not null
);
create table orders (
id bigint primary key,
user_id bigint,
total_amount decimal(10, 2) not null
);假设数据如下:user
| id | username |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
orders
| id | user_id | total_amount |
|---|---|---|
| 101 | 1 | 100.00 |
| 102 | 1 | 200.00 |
| 103 | 2 | 150.00 |
| 104 | 9 | 80.00 |
这里故意保留一条 user_id = 9 的订单,用来观察不同连接方式的差异。
4. 内连接 inner join
4.1 它是什么
内连接只返回两张表中能匹配上的数据。
也就是说:左表一条记录,如果在右表里找不到满足 on 条件的匹配项,这条结果就不会出现在最终结果里。
4.2 示例
sql
select
o.id as order_id,
o.user_id,
u.username
from orders o
inner join user u on o.user_id = u.id;结果大致会是:
| order_id | user_id | username |
|---|---|---|
| 101 | 1 | Alice |
| 102 | 1 | Alice |
| 103 | 2 | Bob |
104 这条订单不会出现,因为 user_id = 9 在 user 表里找不到匹配记录。
4.3 应用场景
内连接适合:
- 两边数据都必须存在时
- 只关心有关联关系的数据时
- 订单必须关联到合法用户、合法商品时
典型场景:
- 查询订单和下单用户
- 查询员工和部门
- 查询订单明细和商品
4.4 特点
- 结果更“干净”,不会保留无关联脏数据
- 适合强业务关联场景
- 如果你发现结果“莫名变少”,要先怀疑是不是内连接把没匹配上的行过滤掉了
5. 左连接 left join
5.1 它是什么
左连接会保留左表全部数据,右表只补充匹配上的部分。
也就是说:即使右表找不到匹配记录,左表这一行也仍然会出现在结果里,只不过右表字段会是 null。
5.2 示例
sql
select
o.id as order_id,
o.user_id,
u.username
from orders o
left join user u on o.user_id = u.id;结果大致会是:
| order_id | user_id | username |
|---|---|---|
| 101 | 1 | Alice |
| 102 | 1 | Alice |
| 103 | 2 | Bob |
| 104 | 9 | null |
5.3 应用场景
左连接适合:
- 主表数据必须保留,关联表可以没有时
- 想查出“有关联”和“没关联”的全部数据时
- 想找出缺失关系数据时
典型场景:
- 查询所有用户及其订单,没有订单的用户也要显示
- 查询所有商品及其库存记录,即使某些商品还没初始化库存
- 查“哪些订单关联不到有效用户”
5.4 特点
- 以左表为准
- 结果更完整,但会出现
null - 很适合做“主数据 + 可选扩展信息”的查询
6. 右连接 right join
6.1 它是什么
右连接和左连接是对称关系:它会保留右表全部数据,左表只补充能匹配上的部分。
6.2 示例
sql
select
o.id as order_id,
o.user_id,
u.username
from orders o
right join user u on o.user_id = u.id;结果大致会是:
| order_id | user_id | username |
|---|---|---|
| 101 | 1 | Alice |
| 102 | 1 | Alice |
| 103 | 2 | Bob |
| null | null | Carol |
因为 Carol 没有订单,但右连接会保留 user 表全部数据。
6.3 应用场景
理论上右连接适合:
- 右表必须保留时
- 逻辑上把右表当主表时
但真实开发里,大多数团队更常用 left join,因为:
- 阅读方向更直观
- 以“主表在左”组织 SQL 更符合习惯
- 几乎所有右连接都可以通过交换表顺序改写成左连接
6.4 实战建议
如果不是为了读历史 SQL 或兼容旧代码,通常优先:把 right join 改写成 left join。
这样更统一,也更容易维护。
7. 交叉连接 cross join
7.1 它是什么
交叉连接会返回两张表的笛卡尔积。
也就是说:左表每一行都会和右表每一行组合一次。
如果左表有 3 行,右表有 4 行,结果就是 12 行。
7.2 示例
sql
select
u.username,
p.name
from user u
cross join product p;7.3 应用场景
交叉连接不常用于普通业务查询,因为结果集会迅速膨胀。
它更适合:
- 生成组合数据
- 生成测试维度
- 做报表模板笛卡尔展开
比如:
- 所有用户和所有活动标签做组合
- 所有日期和所有门店做报表模板
7.4 风险
如果不是明确需要笛卡尔积,误写交叉连接通常会导致:
- 结果数量暴涨
- 查询性能变差
- 业务结果完全不对
所以这一类 SQL 要格外谨慎。
8. 自连接 self join
8.1 它是什么
自连接不是一种新的关键字,而是:一张表和自己进行连接。
这通常借助表别名来完成。
8.2 示例
假设员工表有 manager_id 字段指向上级员工:
sql
select
e.id,
e.name as employee_name,
m.name as manager_name
from employee e
left join employee m on e.manager_id = m.id;这里:
e表示员工自己m表示员工的上级- 本质上是同一张表在不同角色下参与连接
8.3 应用场景
自连接适合:
- 树形或层级结构
- 上下级关系
- 同类实体之间的关联关系
典型场景:
- 员工与直属领导
- 分类与父分类
- 评论与父评论
9. 🌟 MySQL 里有没有全连接 full join
标准 SQL 里还有一种常见连接叫:full outer join
它的语义是:左表和右表都保留,匹配上的合并,不匹配的部分用 null 补齐。
但要注意:MySQL 本身不直接支持 full outer join 语法。
如果一定要实现类似效果,常见做法是:
- 先做
left join - 再做
right join - 用
union或union all合并
例如:
sql
select
o.id as order_id,
o.user_id,
u.username
from orders o
left join user u on o.user_id = u.id
union
select
o.id as order_id,
o.user_id,
u.username
from orders o
right join user u on o.user_id = u.id;10. 🌟 on 和 where 的区别
这是连接里最容易讲混的点之一。
先说结论:
on用来描述表和表怎么匹配where用来对连接后的结果再做过滤
在 inner join 里,两者很多时候看起来结果一样,但在 left join 里差异会非常明显。
10.1 放在 on 里的过滤
sql
select
u.id,
u.username,
o.id as order_id
from user u
left join orders o
on u.id = o.user_id
and o.total_amount > 100;含义是:
- 先保留所有用户
- 只有订单金额大于 100 的订单才会被补进来
- 没有符合条件订单的用户仍然保留
10.2 放在 where 里的过滤
sql
select
u.id,
u.username,
o.id as order_id
from user u
left join orders o on u.id = o.user_id
where o.total_amount > 100;这时候效果会变成:
- 先做左连接
- 再把
o.total_amount <= 100或o.total_amount is null的行过滤掉 - 很多原本应该保留的左表记录也没了
于是这条 SQL 的结果看起来就更接近内连接了。
10.3 实战经验
如果你的目标是:
- 保留左表全部数据
- 只限制右表补进来的记录
那么过滤条件更适合写在 on 里。
11. 各种连接方式的核心区别
你可以把它们压缩成下面这张表来记:
| 连接方式 | 保留哪边数据 | 匹配不到时表现 | 常见用途 |
|---|---|---|---|
inner join | 只保留两边都匹配上的 | 直接丢掉 | 强关联查询 |
left join | 保留左表全部数据 | 右表字段补 null | 主表必须保留 |
right join | 保留右表全部数据 | 左表字段补 null | 较少直接使用 |
cross join | 左右全部做组合 | 不谈匹配,直接笛卡尔积 | 生成组合数据 |
self join | 本质取决于具体写法 | 取决于 inner/left 等组合 | 层级、自关联关系 |
12. 不同连接方式分别适合什么场景
12.1 inner join
适合:
- 只查合法关联数据
- 业务上两边都必须存在
- 不关心缺失关联的数据
例子:
- 查询某订单对应的用户
- 查询订单明细对应的商品
12.2 left join
适合:
- 主表数据必须全部保留
- 右表是扩展信息
- 要找“没有关联记录”的情况
例子:
- 查所有用户及其最近一笔订单
- 查所有商品及其库存
- 查没有下过单的用户
12.3 right join
适合:
- 历史 SQL 就这么写
- 逻辑上更想保留右表
但新代码通常更建议改写成 left join。
12.4 cross join
适合:
- 维度笛卡尔组合
- 报表模板补全
- 测试数据生成
12.5 self join
适合:
- 员工上下级
- 分类树
- 评论树
13. 🌟 连接查询为什么容易慢
连接慢并不是因为 join 这个词本身有问题,而是因为它天然会扩大查询复杂度。
常见原因包括:
- 连接字段没有索引
- 返回字段太多
- 连接表太多
- 先过滤不充分,参与连接的数据集过大
- 把本应写在
on或where的条件写乱了
13.1 一个基本优化思路
连接查询优化时,通常看:
- 连接字段是否有索引
- 主表和驱动表是否合理
- 是否能先缩小数据范围再连接
- 是否只查询必要字段
explain执行计划是否符合预期
14. 怎么把连接讲清楚
如果想把连接这件事压缩成一段顺畅的说明,可以这样表述:
连接就是把多张表中相关的数据按某个关联条件拼到一条结果里。最常见的连接方式有 inner join、left join、right join、cross join 和 self join。inner join 只保留两边都匹配上的数据;left join 保留左表全部数据,右表匹配不上时补 null;right join 和 left join 对称,但实际开发里通常更常用 left join;cross join 会产生笛卡尔积,一般只在特殊组合场景使用;self join 则是一张表和自己关联,常见于层级结构。选择哪种连接方式,本质取决于你希望保留哪一边的数据,以及业务上是否允许关联缺失。
15. 总结
把连接真正学明白,重点不是背关键字,而是想清楚两个问题:
- 哪张表的数据必须保留
- 匹配不上时你希望结果怎么表现
只要抓住这两个问题,大多数连接方式都不难判断:
- 都要匹配上,用
inner join - 左边必须保留,用
left join - 右边必须保留,用
right join - 要所有组合,用
cross join - 同表关联,用
self join
再往前一步,就是把 on、where、索引和执行计划也一起考虑进去。这样你就不是“会写 join”,而是真的理解了 join。