在关系型数据库里,JOIN和子查询是实现关联计算的两种常见写法。从语义上它们经常可以互相改写,但执行时的性能差异往往很大。要理解这种差异,不能只凭经验,而要看数据库优化器背后的成本模型是怎么估算的。
什么是成本模型
成本模型是数据库优化器用来估算不同执行计划开销的数学模型。它主要考虑以下因素:
- 磁盘IO次数,包括顺序读和随机读
- CPU计算量,比如比较、排序、聚合
- 内存使用,尤其是哈希表和排序缓冲区
- 表的基数(行数)和列的分布统计信息
优化器会为每一种可能的计划算出一个成本值,选最小的那个。因此JOIN和子查询谁更快,本质上取决于哪种写法能让优化器算出更低的成本。
JOIN的典型执行路径
对于两表JOIN,优化器常选择嵌套循环、哈希连接或合并连接。当两表都有索引时,合并连接成本很低;大表无索引时,哈希连接虽然吃内存,但只需扫描一次。
-- 使用JOIN获取订单及用户姓名 SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.amount > 100;
上面的JOIN写法通常会被优化器展开为一次关联扫描,用户表若按主键查找则IO可控。
子查询的成本陷阱
子查询分为相关子查询和非相关子查询。非相关子查询可被提前物化,而相关子查询往往对外部每一行都执行一次,成本随外行数线性增长。
-- 使用相关子查询 SELECT o.id, (SELECT u.name FROM users u WHERE u.id = o.user_id) AS name FROM orders o WHERE o.amount > 100;
若优化器没有做子查询展开(subquery flattening),这个写法会对orders中每条满足条件的记录都查一次users,成本模型会给出很高的CPU和IO估值。
成本模型下的对比示例
假设orders有十万行,users有一万行,users.id为主键。我们看两种写法的估算差异:
| 写法 | 扫描次数 | 估算成本 |
|---|---|---|
| JOIN | 各表一次 | 1200 |
| 相关子查询 | 外部十万次 | 85000 |
从表可以看出,当外部表大时,相关子查询的成本模型值远高于JOIN。
如何利用成本模型做选择
写SQL时可以用EXPLAIN命令查看优化器给出的计划和成本:
EXPLAIN SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.amount > 100;
如果发现子查询被展开成了JOIN,说明优化器认为二者成本等价;若子查询仍在计划里出现多次,就应改写成JOIN或用好索引。
建议
- 优先写JOIN,更易被优化器优化
- 避免相关子查询,或确认统计信息最新
- 用EXPLAIN比对两种写法的成本值
成本模型不是绝对真理,但它揭示了数据库如何看待你的SQL。理解它,才能写出既清晰又快的查询。