多表连接 JOIN
本教程共 46 篇 · 第 27 篇 · 更新于 2026-07-30 · 约 12 分钟阅读
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;
拆解一下:
FROM users u— 从 users 表开始,起别名 uINNER JOIN orders o— 连接 orders 表,起别名 oON 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 查询中,ON 和 WHERE 都能写条件,但执行时机不同:
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,被过滤)
WarningLEFT 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;
NoteJOIN 查询中建议给每个列起别名(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 要确保连接列有索引,别连太多表。下一章学自连接和交叉连接。