Skip to content

MySQL 连接详解

很多人在学 MySQL 时,会把连接记成几条语法:

  1. inner join
  2. left join
  3. right join
  4. cross join

但如果只背语法,到了真实业务里很容易混淆:

  1. 到底什么时候该用内连接,什么时候该用左连接
  2. onwhere 的过滤到底差在哪
  3. 为什么有些连接会把数据“连丢”
  4. 为什么有些查询一 join 就变慢

这篇文章就专门把 MySQL 里的“连接”讲清楚:它是什么、有哪些方式、彼此有什么区别,以及分别适合什么场景。

1. 什么是连接

连接,也就是 join,本质上是在多张表之间按照某种关联条件,把相关数据拼到一条查询结果里。

你可以把它理解成:单表查询是在一张表里找数据,连接查询是在多张表里把相关数据拼起来再返回。

例如:

  1. 订单表里有 user_id
  2. 用户表里有 id
  3. 当我们想查“订单是谁下的”时,就需要把订单表和用户表连起来

典型 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;

这里的意思就是:

  1. orders 为主
  2. user 表里找 id = o.user_id 的用户
  3. 把订单和用户名一起查出来

2. 连接到底在解决什么问题

关系型数据库把数据拆成多张表,本质上是为了:

  1. 降低冗余
  2. 保持数据一致性
  3. 让结构更清晰

但表一拆开,查询时就会遇到一个新问题:一条业务数据,往往分散在多张表里。

比如一个订单详情页,通常需要:

  1. 订单主信息
  2. 下单用户信息
  3. 商品明细信息
  4. 商品名称和价格信息

这时候如果完全不用连接,就只能:

  1. 先查订单
  2. 再查用户
  3. 再查订单明细
  4. 再查商品

这样写当然也行,但 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

idusername
1Alice
2Bob
3Carol

orders

iduser_idtotal_amount
1011100.00
1021200.00
1032150.00
104980.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_iduser_idusername
1011Alice
1021Alice
1032Bob

104 这条订单不会出现,因为 user_id = 9user 表里找不到匹配记录。

4.3 应用场景

内连接适合:

  1. 两边数据都必须存在时
  2. 只关心有关联关系的数据时
  3. 订单必须关联到合法用户、合法商品时

典型场景:

  1. 查询订单和下单用户
  2. 查询员工和部门
  3. 查询订单明细和商品

4.4 特点

  1. 结果更“干净”,不会保留无关联脏数据
  2. 适合强业务关联场景
  3. 如果你发现结果“莫名变少”,要先怀疑是不是内连接把没匹配上的行过滤掉了

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_iduser_idusername
1011Alice
1021Alice
1032Bob
1049null

5.3 应用场景

左连接适合:

  1. 主表数据必须保留,关联表可以没有时
  2. 想查出“有关联”和“没关联”的全部数据时
  3. 想找出缺失关系数据时

典型场景:

  1. 查询所有用户及其订单,没有订单的用户也要显示
  2. 查询所有商品及其库存记录,即使某些商品还没初始化库存
  3. 查“哪些订单关联不到有效用户”

5.4 特点

  1. 以左表为准
  2. 结果更完整,但会出现 null
  3. 很适合做“主数据 + 可选扩展信息”的查询

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_iduser_idusername
1011Alice
1021Alice
1032Bob
nullnullCarol

因为 Carol 没有订单,但右连接会保留 user 表全部数据。

6.3 应用场景

理论上右连接适合:

  1. 右表必须保留时
  2. 逻辑上把右表当主表时

但真实开发里,大多数团队更常用 left join,因为:

  1. 阅读方向更直观
  2. 以“主表在左”组织 SQL 更符合习惯
  3. 几乎所有右连接都可以通过交换表顺序改写成左连接

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 应用场景

交叉连接不常用于普通业务查询,因为结果集会迅速膨胀。

它更适合:

  1. 生成组合数据
  2. 生成测试维度
  3. 做报表模板笛卡尔展开

比如:

  1. 所有用户和所有活动标签做组合
  2. 所有日期和所有门店做报表模板

7.4 风险

如果不是明确需要笛卡尔积,误写交叉连接通常会导致:

  1. 结果数量暴涨
  2. 查询性能变差
  3. 业务结果完全不对

所以这一类 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;

这里:

  1. e 表示员工自己
  2. m 表示员工的上级
  3. 本质上是同一张表在不同角色下参与连接

8.3 应用场景

自连接适合:

  1. 树形或层级结构
  2. 上下级关系
  3. 同类实体之间的关联关系

典型场景:

  1. 员工与直属领导
  2. 分类与父分类
  3. 评论与父评论

9. 🌟 MySQL 里有没有全连接 full join

标准 SQL 里还有一种常见连接叫:full outer join

它的语义是:左表和右表都保留,匹配上的合并,不匹配的部分用 null 补齐。

但要注意:MySQL 本身不直接支持 full outer join 语法。

如果一定要实现类似效果,常见做法是:

  1. 先做 left join
  2. 再做 right join
  3. unionunion 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. 🌟 onwhere 的区别

这是连接里最容易讲混的点之一。

先说结论:

  1. on 用来描述表和表怎么匹配
  2. 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;

含义是:

  1. 先保留所有用户
  2. 只有订单金额大于 100 的订单才会被补进来
  3. 没有符合条件订单的用户仍然保留

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;

这时候效果会变成:

  1. 先做左连接
  2. 再把 o.total_amount <= 100o.total_amount is null 的行过滤掉
  3. 很多原本应该保留的左表记录也没了

于是这条 SQL 的结果看起来就更接近内连接了。

10.3 实战经验

如果你的目标是:

  1. 保留左表全部数据
  2. 只限制右表补进来的记录

那么过滤条件更适合写在 on 里。

11. 各种连接方式的核心区别

你可以把它们压缩成下面这张表来记:

连接方式保留哪边数据匹配不到时表现常见用途
inner join只保留两边都匹配上的直接丢掉强关联查询
left join保留左表全部数据右表字段补 null主表必须保留
right join保留右表全部数据左表字段补 null较少直接使用
cross join左右全部做组合不谈匹配,直接笛卡尔积生成组合数据
self join本质取决于具体写法取决于 inner/left 等组合层级、自关联关系

12. 不同连接方式分别适合什么场景

12.1 inner join

适合:

  1. 只查合法关联数据
  2. 业务上两边都必须存在
  3. 不关心缺失关联的数据

例子:

  1. 查询某订单对应的用户
  2. 查询订单明细对应的商品

12.2 left join

适合:

  1. 主表数据必须全部保留
  2. 右表是扩展信息
  3. 要找“没有关联记录”的情况

例子:

  1. 查所有用户及其最近一笔订单
  2. 查所有商品及其库存
  3. 查没有下过单的用户

12.3 right join

适合:

  1. 历史 SQL 就这么写
  2. 逻辑上更想保留右表

但新代码通常更建议改写成 left join

12.4 cross join

适合:

  1. 维度笛卡尔组合
  2. 报表模板补全
  3. 测试数据生成

12.5 self join

适合:

  1. 员工上下级
  2. 分类树
  3. 评论树

13. 🌟 连接查询为什么容易慢

连接慢并不是因为 join 这个词本身有问题,而是因为它天然会扩大查询复杂度。

常见原因包括:

  1. 连接字段没有索引
  2. 返回字段太多
  3. 连接表太多
  4. 先过滤不充分,参与连接的数据集过大
  5. 把本应写在 onwhere 的条件写乱了

13.1 一个基本优化思路

连接查询优化时,通常看:

  1. 连接字段是否有索引
  2. 主表和驱动表是否合理
  3. 是否能先缩小数据范围再连接
  4. 是否只查询必要字段
  5. 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. 总结

把连接真正学明白,重点不是背关键字,而是想清楚两个问题:

  1. 哪张表的数据必须保留
  2. 匹配不上时你希望结果怎么表现

只要抓住这两个问题,大多数连接方式都不难判断:

  1. 都要匹配上,用 inner join
  2. 左边必须保留,用 left join
  3. 右边必须保留,用 right join
  4. 要所有组合,用 cross join
  5. 同表关联,用 self join

再往前一步,就是把 onwhere、索引和执行计划也一起考虑进去。这样你就不是“会写 join”,而是真的理解了 join。

基于 VitePress 构建的个人技术笔记。