首页 / MySQL 入门教程 / 多表连接 JOIN

MySQL 入门教程

多表连接 JOIN

本教程共 46 篇 · 第 27 篇 · 更新于 2026-07-30 · 约 12 分钟阅读

MySQLMySQL 入门教程JOININNER JOINLEFT JOINRIGHT JOIN多表连接

27. 多表连接 JOIN

本节目标:彻底搞懂 MySQL 三种 JOIN 的区别:INNER JOIN 只取两表都匹配的行、LEFT JOIN 保留左表所有行、RIGHT JOIN 保留右表所有行,学会 ON 条件写法、多表连接和 JOIN 性能要点。

27.1 为什么要 JOIN

数据分散在多张表里。用户信息在 users 表,订单信息在 orders 表。想看”张三买了什么”,就得把两张表连起来查。

JOIN 就是把多张表按某个关联条件拼成一张宽表,让你同时看到来自不同表的数据。

关联条件通常是外键关系:orders 表的 user_id 指向 users 表的 id

27.2 INNER JOIN:内连接

INNER JOIN 只返回两表都能匹配上的行。匹配不上的行,两边都不要。

SELECT u.username, o.amount, o.status
FROM users u
INNER JOIN orders o ON o.user_id = u.id;

拆解一下:

  1. FROM users u — 从 users 表开始,起别名 u
  2. INNER JOIN orders o — 连接 orders 表,起别名 o
  3. ON o.user_id = u.id — 连接条件:orders 的 user_id 等于 users 的 id

结果只包含有订单的用户及其订单。没有订单的用户不会出现,没有对应用户的订单(孤儿订单)也不会出现。

users 表:              orders 表:
+----+------+         +----+---------+--------+
| id | name |         | id | user_id | amount |
+----+------+         +----+---------+--------+
|  1 | 张三 |         |  1 |    1    |  100   |
|  2 | 李四 |         |  2 |    1    |  200   |
|  3 | 王五 |         |  3 |    2    |  150   |
+----+------+         +----+---------+--------+

INNER JOIN 结果:
+----+------+----+---------+--------+
| id | name | id | user_id | amount |
+----+------+----+---------+--------+
|  1 | 张三 |  1 |    1    |  100   |
|  1 | 张三 |  2 |    1    |  200   |
|  2 | 李四 |  3 |    2    |  150   |
+----+------+----+---------+--------+
王五没有订单,不在结果中
Note

INNER JOIN 可以简写为 JOIN,效果完全一样。但建议写全 INNER JOIN,语义更清晰。

27.3 LEFT JOIN:左连接

LEFT JOIN 保留左表的所有行,右表匹配不上的用 NULL 填充。

SELECT u.username, o.amount, o.status
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;

users 在左边,orders 在右边。所有用户都会出现,没有订单的用户的 amount 和 status 显示为 NULL。

LEFT JOIN 结果:
+----+------+----+---------+--------+
| id | name | id | user_id | amount |
+----+------+----+---------+--------+
|  1 | 张三 |  1 |    1    |  100   |
|  1 | 张三 |  2 |    1    |  200   |
|  2 | 李四 |  3 |    2    |  150   |
|  3 | 王五 |NULL|  NULL   |  NULL  |  <- 王五没订单,用 NULL 填充
+----+------+----+---------+--------+

LEFT JOIN 是实际开发中用得最多的 JOIN。因为很多时候你需要”全部 A,顺便带上关联的 B”。

用 LEFT JOIN 找”没有关联”的行

-- 查没有订单的用户
SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;

先左连接保留所有用户,再过滤出右表为 NULL 的(就是没订单的)。这是 LEFT JOIN 的经典用法。

27.4 RIGHT JOIN:右连接

RIGHT JOIN 跟 LEFT JOIN 对称,保留右表的所有行,左表匹配不上的用 NULL 填充。

SELECT u.username, o.amount, o.status
FROM users u
RIGHT JOIN orders o ON o.user_id = u.id;

所有订单都会出现。如果某条订单的 user_id 在 users 表里找不到对应用户(孤儿订单),username 显示为 NULL。

Tip

RIGHT JOIN 用得少。因为同样的结果可以调换表顺序用 LEFT JOIN 实现,更符合从左到右的阅读习惯:

-- 这两条等价
SELECT * FROM users u RIGHT JOIN orders o ON o.user_id = u.id;
SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id;

实际开发中,习惯用 LEFT JOIN,把”主表”放左边。

27.5 三种 JOIN 对比

JOIN 类型保留哪边匹配不上的行典型用途
INNER JOIN只保留匹配的都丢弃查两表都有的关联数据
LEFT JOIN左表全保留右表填 NULL查全部左表+关联数据
RIGHT JOIN右表全保留左表填 NULL查全部右表+关联数据

用维恩图理解:

INNER JOIN:    两圆相交的部分
LEFT JOIN:     左圆全部(相交部分 + 左圆独有)
RIGHT JOIN:    右圆全部(相交部分 + 右圆独有)
Note

还有一个 FULL OUTER JOIN(全外连接),保留两表所有行。但 MySQL 不支持 FULL OUTER JOIN。需要的话用 UNION 合并 LEFT JOIN 和 RIGHT JOIN:

SELECT * FROM users u LEFT JOIN orders o ON o.user_id = u.id
UNION
SELECT * FROM users u RIGHT JOIN orders o ON o.user_id = u.id;

27.6 ON 和 WHERE 的区别

在 JOIN 查询中,ONWHERE 都能写条件,但执行时机不同:

  • ON:连接条件,决定两表怎么匹配
  • WHERE:过滤条件,对连接后的结果做过滤

对 INNER JOIN 来说,写在 ON 还是 WHERE 效果一样:

-- 这两条等价
SELECT * FROM users u
INNER JOIN orders o ON o.user_id = u.id AND u.age > 25;

SELECT * FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE u.age > 25;

但对 LEFT JOIN 有区别:

-- 条件写在 ON:保留所有用户,只是 orders 的 amount > 100 才匹配
SELECT u.username, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.amount > 100;
-- 没订单的用户也会出现(o.amount 为 NULL)

-- 条件写在 WHERE:先连接再过滤,没订单的用户被过滤掉
SELECT u.username, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.amount > 100;
-- 没订单的用户不会出现(NULL > 100 为 NULL,被过滤)
Warning

LEFT JOIN 中把右表的过滤条件写在 WHERE 里,会”退化”成 INNER JOIN 的效果。如果你用 LEFT JOIN 是为了保留左表所有行,右表的过滤条件要写在 ON 里,不是 WHERE 里。这个坑很多人踩过。

27.7 多表连接

JOIN 不限于两张表。可以连续 JOIN 多张表:

SELECT u.username, o.amount, o.status
FROM users u
INNER JOIN orders o ON o.user_id = u.id
INNER JOIN order_items oi ON oi.order_id = o.id;

连接顺序是从左到右:先 users JOIN orders,结果再 JOIN order_items。

连接多张表的性能

每多一个 JOIN,数据库要做更多匹配工作。如果表上有合适的索引(特别是 JOIN 条件的列),性能能得到保障。

-- 确保 user_id 和 id 上有索引
-- users.id 是主键(有索引)
-- orders.user_id 应该建索引
Tip

多表 JOIN 时,驱动表(FROM 后面的第一张表)的选择很重要。MySQL 优化器通常会选择小表作为驱动表。但你也可以通过写法影响选择。一般来说,把过滤后结果更小的表放前面更好。

27.8 JOIN 的别名和列引用

多表 JOIN 时,不同表可能有同名列(比如都有 id、created_at)。这时必须用 表名.列名别名.列名 来区分:

SELECT
    u.id AS 用户ID,
    u.username,
    o.id AS 订单ID,
    o.amount,
    u.created_at AS 用户注册时间,
    o.created_at AS 下单时间
FROM users u
INNER JOIN orders o ON o.user_id = u.id;
Note

JOIN 查询中建议给每个列起别名(AS),让结果清晰可读。特别是 id、created_at 这种多个表都有的通用列名,不起别名根本分不清是谁的。

27.9 JOIN 的替代语法

MySQL 还支持一种旧的逗号连接语法:

-- 旧写法(逗号连接)
SELECT u.username, o.amount
FROM users u, orders o
WHERE o.user_id = u.id;

-- 等价于
SELECT u.username, o.amount
FROM users u
INNER JOIN orders o ON o.user_id = u.id;
Warning

逗号连接语法不推荐使用。它只能做 INNER JOIN,不支持 LEFT/RIGHT JOIN,而且连接条件和过滤条件混在 WHERE 里,可读性差。新代码统一用 JOIN ... ON 语法。

27.10 JOIN 性能要点

确保 JOIN 条件列有索引

-- orders.user_id 上要有索引
-- 否则每次匹配都要全表扫描 orders
SELECT * FROM users u
INNER JOIN orders o ON o.user_id = u.id;

小表驱动大表

让小结果集作为驱动表(FROM 后面的表),减少内层循环次数。MySQL 优化器通常会自动选择,但了解原理有助于排查问题。

避免连接太多表

JOIN 3-5 张表是常见的,超过 7 张表就要考虑设计是否合理了。太多 JOIN 会导致执行计划复杂、性能下降。

只 SELECT 需要的列

-- 不好:SELECT * 拖慢查询
SELECT * FROM users u
INNER JOIN orders o ON o.user_id = u.id;

-- 好:只取需要的列
SELECT u.username, o.amount, o.status
FROM users u
INNER JOIN orders o ON o.user_id = u.id;

27.11 小结

JOIN 是 SQL 的核心能力。INNER JOIN 只取匹配行,LEFT JOIN 保留左表全部,RIGHT JOIN 保留右表全部。LEFT JOIN 最常用,特别是”查全部 A + 关联的 B”和”找没有关联的行”。记住 ON 和 WHERE 在 LEFT JOIN 中的区别,右表过滤条件写 ON 里才能保留左表全部行。多表 JOIN 要确保连接列有索引,别连太多表。下一章学自连接和交叉连接。