INNER JOIN 是什么
INNER JOIN 是内连接,只返回两侧都能匹配上的行,本质是在两张表的组合里按连接条件取交集。MySQL 8.0 中 INNER JOIN、逗号连接与把条件写进 WHERE 是等价的,优化器自己决定谁做驱动表、按什么顺序连接,因此性能关键不在书写顺序,而在被驱动表的连接列上有没有索引。缺索引时被驱动表会被反复全表扫描,代价随驱动表行数成倍放大。
基本信息
| 用途 | 取两表交集,横向拼接列 |
|---|---|
| 语法 | A INNER JOIN B ON 连接条件 |
| 等价写法 | FROM A, B WHERE A.列 = B.列 |
| 执行方式 | 嵌套循环连接为主,无可用索引时 8.0.18 起可选哈希连接 |
| 关键前提 | 连接列在两侧都要有索引,尤其被驱动表 |
| 条件分工 | 连接条件写 ON,普通过滤条件写 WHERE |
典型使用场景
- 订单列表要展示下单人,用订单表连接用户表取昵称与等级
- 报表按维表补全名称,例如渠道、地区、商品类目
- 权限判断:连接角色授权表确认某个用户是否存在有效授权
- 数据核对:找出同时存在于业务表与日志表中的记录
- 多表明细导出,控制返回列避免生成过宽的结果集
- 统计有订单的用户数,用于确认引用完整性
用法示例
用法示例
SELECT o.id, o.amount, u.name # 只取需要的列
FROM orders o
INNER JOIN users u ON u.id = o.user_id # 连接列两侧类型必须一致
WHERE o.status = 'paid' # 普通过滤条件放在 WHERE
LIMIT 50; # 明细查询务必限制返回行数多表连接
多表连接
SELECT o.id, u.name, p.title
FROM orders o
JOIN users u ON u.id = o.user_id # 行数少的表做驱动表通常更划算
JOIN products p ON p.id = o.product_id
WHERE o.created_at >= '2026-10-01'; # 过滤条件先收窄数据量执行计划逐列读法
| 列 | 含义 | 正常与异常 |
|---|---|---|
| id | 执行顺序编号,同号按从上到下执行 | 顺序与书写顺序无关,别按语句顺序读 |
| table | 当前访问的表 | 排在前面的通常是驱动表 |
| type | 访问类型 | const / eq_ref / ref 较好,ALL 表示全表扫描 |
| possible_keys | 可用索引 | 为空说明连接列上没有可用索引 |
| key | 实际使用的索引 | 为 NULL 时需要检查连接列的索引 |
| rows | 预估扫描行数 | 被驱动表数值很大说明连接在放大 |
| Extra | 附加信息 | 出现 Using join buffer 说明没走索引连接 |
EXPLAIN 里出现 Using join buffer 且预估行数很大,基本就是被驱动表的连接列缺索引:这时连接代价随驱动表行数成倍放大,补索引是最直接的优化。
MySQL 8.0.18 起,连接列没有可用索引时优化器可能选择哈希连接,性能优于嵌套循环扫描,但仍不如走索引的连接,不能当作不建索引的理由。
漏写连接条件会产生笛卡尔积,两表行数相乘,大表上足以瞬间打满临时空间与磁盘;多表查询写完后先确认返回行数是否符合预期,再交给业务使用。
连接性能检查清单
- 被驱动表的连接列必须有索引,执行计划里的访问类型应落在等值或引用级别
- 连接列两侧类型与字符集保持一致,避免隐式转换让索引失效
- 先过滤再连接:把高选择性条件写进 WHERE,减少参与连接的行数
- 只取需要的列,避免连接后大量回表读取宽列
- 多表连接时用行数少的表做驱动表,减少内层循环次数
- 不要在连接条件里对列做函数运算,表达式会阻断索引使用
连接结果行数异常放大时先检查是否漏写条件或存在多重匹配:一对多关系下同一条主记录会出现多次,业务侧应做去重或聚合,而不是靠限制返回行数掩盖问题。