子查询总拖慢查询?把它变成连接就能解决?
url: /posts/79c590fbd87ece535b11a71c9667884f/
title: 子查询总拖慢查询?把它变成连接就能解决?
date: 2025-10-21T03:54:12+08:00
lastmod: 2025-10-21T03:54:12+08:00
author: cmdragon
summary:
子查询是嵌套在其他SQL语句中的查询,分为标量、行、列和表子查询。相关子查询依赖外部查询变量,性能较差,而非相关子查询独立执行。PostgreSQL优化器通过子查询扁平化将子查询转换为更高效的连接操作。连接操作包括内连接、外连接和交叉连接,优化器基于成本模型选择连接顺序和方法,如嵌套循环、哈希和排序合并连接。通过改写子查询为连接或使用LATERAL连接,可以显著提升查询性能。
categories:
- postgresql
tags:
- 基础入门
- PostgreSQL
- 子查询
- 连接优化
- SQL性能优化
- LATERAL连接
- 查询改写


扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/
一、子查询:从基础到进阶
子查询是嵌套在其他SQL语句中的查询,相当于“查询里的查询”。PostgreSQL支持多种类型的子查询,我们可以根据返回结果的结构和依赖关系来分类。
1.1 子查询的基本类型
根据返回结果的维度,子查询可以分为四类:
- 标量子查询:返回单个值(一行一列),常用于
SELECT
、WHERE
或HAVING
子句中。例如:-- 查询所有商品的最高价格,作为单独列返回 SELECT product_name, price, (SELECT MAX(price) FROM products) AS max_price FROM products;
- 行子查询:返回一行多列,需用括号包裹列组合。例如:
-- 查找与管理员邮箱匹配的用户(假设id和name唯一) SELECT * FROM users WHERE (id, name) = (SELECT id, name FROM admins WHERE email = 'admin@example.com');
- 列子查询:返回一列多行,常用于
IN
、ANY
或ALL
运算符后。例如:-- 查找购买过金额超过100元订单的用户 SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
- 表子查询:返回多行多列,需作为临时表(别名)使用,常用于
FROM
子句中。例如:-- 查找总订单金额超过1000元的用户 SELECT * FROM (SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id ) AS order_totals WHERE total > 1000;
1.2 相关子查询 vs 非相关子查询
根据是否依赖外部查询的变量,子查询分为:
- 非相关子查询:子查询不引用外部查询的任何列,只执行一次。例如上文中的“查找购买过金额超过100元订单的用户”,子查询
SELECT user_id FROM orders WHERE amount > 100
独立于外部的users
表。 - 相关子查询:子查询引用了外部查询的列,每次外部查询的行都会触发子查询执行。例如:
相关子查询的性能通常更差——想象一下,如果你有1000个用户,子查询就要执行1000次!-- 查找有订单金额超过100元的用户(相关子查询依赖u.id) SELECT name FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 100);
1.3 PostgreSQL对子查询的处理:扁平化与去相关
PostgreSQL的查询优化器(Query Optimizer)会尝试将可优化的子查询转换为更高效的连接操作,这个过程叫子查询扁平化(Subquery Flattening)。例如,对于非相关的IN
子查询:
-- 原写法:IN子查询
SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
优化器会将其“扁平化”为等价的内连接:
-- 优化后:内连接(自动去重)
SELECT DISTINCT u.name
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;
为什么要这么做?因为连接操作的执行计划(如哈希连接、嵌套循环)通常比子查询更高效,尤其是当子查询返回大量结果时。
但不是所有子查询都能被扁平化——比如相关子查询或返回多列的子查询,优化器无法直接转换,这时需要我们手动改写(后面会讲)。
二、连接优化:理解连接的“底层逻辑”
连接是SQL中组合多个表