INNER JOIN 是什么
INNER JOIN 按连接条件把两张表的行配对,只保留两边都匹配成功的组合,任何一边没有匹配的行都会从结果里消失。显式写 JOIN … ON 比逗号加 WHERE 的隐式连接更清晰,也不容易在多加一张表时漏写连接条件而变成笛卡尔积。连接在存储引擎层的实现通常是嵌套循环或哈希匹配,被驱动表的连接列上有索引能大幅降低代价。多表连接时优化器自行决定驱动顺序,一般倾向把小结果集作为驱动表。
基本信息
| 作用 | 按条件配对两表行,只保留匹配成功的组合 |
|---|---|
| 语法 | SELECT … FROM a INNER JOIN b ON a.id = b.a_id |
| 等价写法 | INNER 可省略;逗号加 WHERE 是老式隐式连接 |
| 连接条件 | 等值与不等值(>、<、BETWEEN)都可以写在 ON |
| 连接键类型 | 两侧类型与字符集建议一致,否则索引可能失效 |
| 输出顺序 | 不保证,需要顺序要显式 ORDER BY |
典型使用场景
- 取明细与主表信息:订单连用户拿到下单人姓名
- 多表串联取完整视图:商品连类目再连品牌
- 用连接代替子查询:IN 子查询改写为 INNER JOIN 通常更高效
- 维度关联:事实表连日期维表、地区维表出报表
- 数据校验:内连接找出两边都存在但对不上的记录
- 自连接:员工表连自身找上级,或找出同城用户配对
多表连接示例
多表连接示例
SELECT o.id, o.amount, u.name AS user_name, p.title
FROM orders AS o # 过滤后的小结果集作驱动表
JOIN order_items AS i ON i.order_id = o.id # 一对多,会放大行数
JOIN products AS p ON p.id = i.product_id # 继续串联
WHERE o.status = 'paid' # 内连接里 WHERE 与 ON 等价
AND o.created_at >= '2024-01-01'
ORDER BY o.id;隐式连接与笛卡尔积
隐式连接与笛卡尔积
-- 老式隐式连接,不推荐
SELECT o.id, u.name FROM orders o, users u WHERE o.user_id = u.id;
-- 漏写连接条件就是笛卡尔积,行数是两表行数相乘
SELECT COUNT(*) FROM orders o JOIN users u; # 危险,结果集爆炸
-- 确实需要全集时显式写 CROSS JOIN,说明这是有意为之
SELECT * FROM sizes CROSS JOIN colors;连接类型对照
| 类型 | 保留哪些行 | 典型用途 |
|---|---|---|
| INNER JOIN | 两边都匹配的行 | 常规取数 |
| LEFT JOIN | 左表全部加匹配的右表 | 主表带可选信息 |
| RIGHT JOIN | 右表全部加匹配的左表 | 可改写成调换顺序的 LEFT JOIN |
| FULL JOIN | 两边全部,缺的补 NULL | MySQL 不支持,需用 UNION 模拟 |
| CROSS JOIN | 两表笛卡尔积 | 生成组合集合 |
内连接里 ON 与 WHERE 可互换;一旦换成外连接,写在 WHERE 的右表条件会把补 NULL 的行过滤掉,语义立刻改变。
连接键两侧类型不一致(如 bigint 对 varchar)会触发隐式转换,被驱动表索引可能失效,EXPLAIN 里会看到 type=ALL。
一对多连接一定会放大结果行数,统计金额前先确认口径:按订单统计要先把明细聚合到订单粒度再连接。
多表连接时漏写其中一个 ON 条件不会报错,只会悄悄产生笛卡尔积,排查时先看结果行数是否异常增大。
连接优化要点
- 驱动表选过滤后行数小的那张:先用 WHERE 把结果集缩小,再参与连接
- 被驱动表的连接列要有索引:没有索引会退化成逐行全表扫描
- 连接键的类型与字符集保持一致:隐式转换会让索引失效
- 先聚合再连接:明细表按关联键聚合到一行后连接,避免行数放大
- 少用 SELECT *:连接后同名列容易冲突,显式列清单也更少读数据
- 多表连接的顺序看 EXPLAIN:通常第一行是驱动表,MySQL 可用 STRAIGHT_JOIN 指定顺序
- 连接列上有函数或表达式时索引失效,把计算移到常量一侧
连接列建议加 NOT NULL 约束,并在建表时统一两侧的字符集与排序规则,能避免大量隐式转换导致的索引失效。