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连接
  • 查询改写

cmdragon_cn.pngcmdragon_cn.png

扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长

发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/

一、子查询:从基础到进阶

子查询是嵌套在其他SQL语句中的查询,相当于“查询里的查询”。PostgreSQL支持多种类型的子查询,我们可以根据返回结果的结构和依赖关系来分类。

1.1 子查询的基本类型

根据返回结果的维度,子查询可以分为四类:

  • 标量子查询:返回单个值(一行一列),常用于SELECTWHEREHAVING子句中。例如:
    -- 查询所有商品的最高价格,作为单独列返回
    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');
  • 列子查询:返回一列多行,常用于INANYALL运算符后。例如:
    -- 查找购买过金额超过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表。
  • 相关子查询:子查询引用了外部查询的列,每次外部查询的行都会触发子查询执行。例如:
    -- 查找有订单金额超过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);
    相关子查询的性能通常更差——想象一下,如果你有1000个用户,子查询就要执行1000次
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中组合多个表