驱动侧与被驱动侧的配对
连接的代价主要取决于两件事:先读的那一侧过滤之后产出多少行,以及被读的那一侧能不能用索引按连接键快速定位。前者决定循环次数,后者决定每次循环的代价,两者相乘就是整体规模。很多连接性能问题不是「SQL 写得不对」,而是被驱动侧的连接列上缺一个索引,或者驱动侧本该过滤到几百行却产出几十万行。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| 驱动侧原则 | 过滤后产出行数越少越好 |
| 被驱动侧原则 | 连接列上要有可用索引 |
| 多表连接 | 逐级裁剪,每层都要检查被驱动列 |
| 哈希连接例外 | 大结果集等值连接时可能不需要索引 |
典型使用场景
- 两张表连接出现数量级差异:检查索引是否建在连接列上
- 多表连接逐层排查:定位是哪一层开始放大的
- 新语句上线前核对:确认每个连接列都有对应索引
- 从单表查询改写成连接后性能下降:核对被驱动侧索引
- 评估可否依赖哈希连接:确认实例版本与数据规模
配对与验证
配对与验证
-- 先给被驱动侧的连接列建索引
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 小结果集在前、大表在后:按连接键逐行查找
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.city = 'shanghai';
-- 用计划验证:确认哪张表作为驱动侧,被驱动侧是否用了索引
EXPLAIN SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.city = 'shanghai';
-- 三张表时逐层检查:每一层的被驱动列都要有索引
-- users → orders → order_items,注意 order_items 的连接列
-- 排序输出列与连接列无关:不要把索引建在输出列上检查清单
| 检查项 | 合格标准 | 不合格的表现 |
|---|---|---|
| 驱动侧过滤能力 | 过滤后行数明显小于表总量 | 全表参与循环 |
| 被驱动侧索引 | 连接列上有可用索引 | 每次循环都全表扫描 |
| 连接条件类型 | 类型一致,避免隐式转换 | 索引因类型不匹配失效 |
| 多表层级 | 每层被驱动列都有索引 | 某一层开始行数爆炸 |
| 输出列 | 不需要索引 | 把索引建在输出列上 |
连接列两边的类型要一致:一边是整数列、另一边是字符列时,隐式转换会让索引直接失效,计划里表现为全表扫描,而 SQL 本身看起来毫无问题。
较新版本在大结果集等值连接时会选择哈希连接,此时被驱动侧没有索引也能跑得不错。这种情况下不要机械地加索引,先看计划再决定。