MySQL联表查询的完整指南,包括连接类型、语法、使用场景和常见问题。
1. 连接类型概览
INNER JOIN(内连接):返回两个表中一致行。
LEFT JOIN(左外连接):返回左表所有行,右表无一致时填充 NULL。
RIGHT JOIN(右外连接):返回右表所有行,左表无一致时填充 NULL(可用 LEFT JOIN 替代,更易读)。
FULL OUTER JOIN:返回两表所有行,无一致处填充 NULL。MySQL 不直接支持,需用 LEFT JOIN UNION RIGHT JOIN 模拟。
CROSS JOIN(交叉连接):返回笛卡尔积(所有组合)。
SELF JOIN(自连接):表和自身连接,必须使用别名。
2. 基本语法和示例
INNER JOIN
sql
SELECT u.name, o.order_no
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
只返回有订单的用户。当条件写在 ON 中时,它既是连接条件也是过滤条件;对于内连接,ON 和 WHERE 结果等价。
LEFT JOIN
sql
SELECT u.name, o.order_no
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
所有用户都会出现,没有订单的用户对应的 order_no 为 NULL。常见需求:找出没有订单的用户:
sql
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.user_id IS NULL;
RIGHT JOIN
sql
SELECT o.order_no, u.name
FROM orders o
RIGHT JOIN users u ON o.user_id = u.id;
效果和上面的 LEFT JOIN 完全相同,但左右表角色互换。建议统一使用 LEFT JOIN,思维负担更小。
模拟 FULL OUTER JOIN
sql
SELECT u.name, o.order_no
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT u.name, o.order_no
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
返回所有用户和所有订单,不一致的侧补 NULL。
CROSS JOIN
sql
SELECT a.color, b.size
FROM colors a
CROSS JOIN sizes b;
生成所有颜色和尺码的组合,常用于生成测试数据或组合矩阵。可省略 CROSS JOIN 直接写逗号连接,但显式写法更清晰。
SELF JOIN
sql
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
同一张表用不同别名区分角色。
3. ON和USING的区别
ON:适用于列名不同或需要复杂条件的场景。
sql
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
USING:当两表的连接列同名时可简化写法,自动合并重复列。
sql
SELECT * FROM users JOIN orders USING (user_id);
结果中 user_id 只出现一次。使用 USING 时,引用该列无需加表别名。
4. 多表连接
可以一次性连接多张表,按顺序书写即可。
sql
SELECT u.name, o.order_no, p.product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE u.vip = 1;
执行顺序:先 users 和 orders 连接,再和 order_items 连接,最后和 products 连接。适当调整连接顺序有时能优化性能。
5. 连接条件和 WHERE 条件的区别
对于 INNER JOIN:ON 和 WHERE 没有思路差别,但建议把关联条件放 ON,业务过滤放 WHERE,更易读。
对于 LEFT/RIGHT JOIN:两者语义完全不同。
写在 ON 中的条件是连接时过滤右表,不影响左表行数。
写在 WHERE 中是对连接后的结果集进行过滤,可能把左表不一致的行也过滤掉。
示例:查询所有用户,以及他们已支付的订单(未支付的不显示订单信息,但用户还在):
sql
SELECT u.name, o.order_no
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid';
如果把 o.status = 'paid' 移到 WHERE,则只有存在已支付订单的用户才会被保留,其他用户被过滤掉。
6. 性能优化
为连接列建立索引:JOIN 的条件列、WHERE 过滤列都应有索引,尤其是被驱动表的连接列。
小表驱动大表:一般让结果集较小的表作为驱动表(写在 JOIN 前面),但优化器会自行调整,可通过 STRAIGHT_JOIN 强制指定。
避免笛卡尔积:缺少 ON 条件的 CROSS JOIN 会产生巨大结果集,确定业务需要。
检查 NULL 的思路:使用 LEFT JOIN 后判断“不存在”时,用 IS NULL 不要用 = NULL。
注意重复行:一对多连接会使结果行数倍增,聚合计算前要用 DISTINCT 或子查询去重。
连接数不宜过多:一次性连接五六张表以上时,考虑分步查询或使用临时表,避免执行计划恶化。