当前位置: 首页 > news >正文

SQL 相关子查询:性能杀手及其优化方法

SQL 相关子查询:性能杀手及其优化方法

您刚刚将新的 SQL 查询部署到生产环境。在测试期间它运行得完美无缺——顺畅、快速,没有问题。但现在,三个小时后,您的老板站在您的办公桌前,问为什么仪表板超时了。

您检查日志。在测试数据库中只有 100 行数据时,这个查询只需 0.2 秒,而在生产环境中 10,000 行数据时,现在却需要 45 秒。同样的查询。同样的逻辑。什么改变了?

让我向您展示罪魁祸首:

-- 找出薪资高于部门平均值的员工
SELECT first_name, last_name, salary, department_id
FROM employees e
WHERE salary > (SELECT AVG(salary)FROM employeesWHERE department_id = e.department_id
);

看起来无辜,对吧?这是一个相关子查询,它运行了 10,001 次而不是一次。到本文结束时,您将知道如何识别这些性能杀手,并用三种不同的方式修复它们。让我们深入探讨。

什么让相关子查询如此缓慢?

这里是您需要理解的关键区别:

非相关子查询运行一次并返回结果:

-- 这只运行一次,然后过滤所有行
SELECT first_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

执行:1 次子查询 + 1 次外层查询 = 总共 2 次操作

相关子查询引用外层查询,并每行运行一次:

-- 这每位员工运行一次
SELECT first_name, salary, department_id
FROM employees e
WHERE salary > (SELECT AVG(salary)FROM employeesWHERE department_id = e.department_id  -- 引用外层查询!
);

执行:10,000 行 × 每行 1 次子查询 = 10,001 次操作

这里是视觉分解:

方面非相关子查询相关子查询
引用外层查询?❌ 否✅ 是
执行次数总共 1 次每行一次
10K 行 =2 次操作10,001 次操作
性能快速 ⚡缓慢 🐌

如何识别它们

在子查询中寻找外层表的引用:

WHERE price > (SELECT AVG(price) FROM products WHERE category_id = p.category_id  -- ⚠️ 引用外层表 "p"
)

💡
经验法则:如果您的子查询内部有 e.、p. 或任何外层表别名,它就是相关的,并且可能很慢。

现在您能识别问题了,让我们修复它。

3 步修复框架

在深入代码之前,这里是您的决策树:

  1. JOIN + GROUP BY — 最适合聚合(AVG、SUM、COUNT)

  2. 窗口函数 — 最适合需要详细行和聚合时

  3. EXISTS 优化 — 最适合存在检查(已经高效,但可以改进)

快速指南:

  • 需要与平均值或总数比较? → JOIN + GROUP BY

  • 需要原始行加上计算? → 窗口函数

  • 只检查是否存在? → EXISTS(或转换为 JOIN)

让我们用真实示例探索每个解决方案。

解决方案 1:重写为 JOIN + GROUP BY

这是聚合比较的最常见修复。策略:先预计算聚合,然后连接到它们。

原始(缓慢) ❌

-- 找出薪资高于部门平均值的员工
SELECT first_name, last_name, salary, department_id
FROM employees e
WHERE salary > (SELECT AVG(salary)FROM employeesWHERE department_id = e.department_id
);
-- 10,000 行 = 10,001 次子查询执行

重写(快速) ✅

-- 预计算部门平均值,然后连接
SELECT e.first_name, e.last_name, e.salary, e.department_id
FROM employees e
JOIN (SELECT department_id, AVG(salary) AS avg_salaryFROM employeesGROUP BY department_id
) dept_avgON e.department_id = dept_avg.department_id
WHERE e.salary > dept_avg.avg_salary;
-- 总共 2 次操作:聚合一次,连接一次

性能差异

方法操作次数时间(10K 行)
相关子查询10,00145 秒 ⏱️
JOIN + GROUP BY20.8 秒 ⚡

改进:快 56 倍!

另一个示例:产品高于类别平均值

-- 使用派生表的快速版本
SELECT p.product_name, p.price, p.category_id, cat_avg.avg_price
FROM products p
JOIN (SELECT category_id, AVG(price) AS avg_priceFROM productsGROUP BY category_id
) cat_avgON p.category_id = cat_avg.category_id
WHERE p.price > cat_avg.avg_price;

为什么有效:内层查询运行一次,为所有类别计算平均值,然后外层查询连接到它。您不是计算平均值 15 次(每个产品一次),而是计算 3 次(每个类别一次)并连接。


专业提示:您可以免费将平均值作为结果中的一列添加——只需在 SELECT 子句中包含 cat_avg.avg_price!

解决方案 2:使用窗口函数

这是优雅的解决方案,当您需要一个结果集中既有细节又有聚合时。

为什么用窗口函数?

有时您想要:

  • 原始行(员工姓名、个人薪资)

  • 聚合计算(部门平均值)

  • 所有在一个干净的结果集中

JOIN 可以做到,但窗口函数更干净、更易读。

原始(缓慢相关) ❌

SELECT first_name,salary,(SELECT AVG(salary)FROM employees e2WHERE e2.department_id = e1.department_id) AS dept_avg
FROM employees e1;
-- 计算平均值 10,000 次!

使用窗口函数重写 ✅

SELECT first_name,salary,department_id,AVG(salary) OVER (PARTITION BY department_id) AS dept_avg,salary - AVG(salary) OVER (PARTITION BY department_id) AS diff_from_avg
FROM employees;
-- 每个部门计算平均值一次,而不是每行一次!

奖金:现在您可以轻松显示每个员工薪资与部门平均值的差异!

真实示例:产品定价分析

SELECT product_name,price,category_id,AVG(price) OVER (PARTITION BY category_id) AS category_avg,price - AVG(price) OVER (PARTITION BY category_id) AS price_vs_avg,RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS price_rank_in_category
FROM products
ORDER BY category_id, price_rank_in_category;

这个单一查询为您提供:

  • 每个产品的价格

  • 类别平均价格

  • 每个产品高于/低于平均值的金额

  • 类别内排名

试试用相关子查询高效地做到这一点!

💡
初学者提示:将 OVER (PARTITION BY ...) 视为“GROUP BY,但保留所有行。”您获得聚合而不折叠结果集。

解决方案 3:优化 EXISTS 模式

这里有个好消息:带有 EXISTS 的相关子查询已经相当高效。一旦找到第一个匹配,它们就会停止搜索。但有时 JOIN 还是更快。

原始(已经相当快) ⚡

-- 找出下过订单的客户
SELECT customer_id, first_name, last_name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
-- 在第一个匹配订单处停止(高效!)

替代:JOIN(大数据集上通常更快) ⚡⚡

-- 相同结果,在大数据集上可能更快
SELECT DISTINCT c.customer_id, c.first_name, c.last_name
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;
-- 现代数据库优化得很好

何时坚持使用 EXISTS

✅ 当以下情况时使用 EXISTS:

  • 您的子查询有多个条件

  • 可读性比 0.1 秒差异更重要

  • 您在检查 NOT EXISTS(比 LEFT JOIN ... WHERE NULL 更安全)

NOT EXISTS 模式

-- 从未下过订单的客户
SELECT customer_id, first_name, last_name
FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);

对于这个模式,坚持使用 NOT EXISTS。它更清晰、更安全。

⚠️
重要:EXISTS 只检查存在性。内部 SELECT 什么并不重要——使用 SELECT 1 作为惯例。它比 SELECT * 略微高效。

您的行动计划:今天修复缓慢查询

准备优化您的查询了吗?遵循这个路线图:

立即行动

  • 审计您的查询——在代码库中搜索引用外层表的子查询:
-- 寻找这样的模式:
WHERE column > (SELECT ... WHERE table.id = outer.id)
WHERE EXISTS (SELECT ... WHERE table.id = outer.id)
  • 基准测试前后——使用 EXPLAIN ANALYZE (PostgreSQL) 或 EXPLAIN (MySQL):
EXPLAIN ANALYZE
SELECT ... -- 您的原始查询EXPLAIN ANALYZE  
SELECT ... -- 您的优化版本

比较执行时间。您应该看到显著改进。

  1. 从最严重的开始——关注:
  • SELECT 子句中带有相关子查询的查询

  • 运行在 10,000+ 行表上的查询

  • 用户抱怨的查询

练习挑战

尝试用三种不同方式重写这个缓慢查询:

-- 挑战:找出价格高于类别平均值的产品
-- 原始(缓慢):
SELECT product_name, price
FROM products p
WHERE price > (SELECT AVG(price) FROM products WHERE category_id = p.category_id
);

轮到您了:

  1. 使用 JOIN + GROUP BY 重写

  2. 使用窗口函数重写

  3. 您会选择哪种方法,为什么?

在评论中发布您的解决方案——我很想看到您的做法!

http://www.dtcms.com/a/593141.html

相关文章:

  • 一文掌握UI自动化测试
  • 金融保险银行营销AI数字化转型培训讲师培训老师唐兴通讲金融银保团队险年金险市场销售
  • 质效飞跃,优测金融数智质效解决方案全新升级!
  • 智网案例精选|光联云网融合智驱,重塑金融数字化转型新格局
  • 自适应网站建设极速建站WordPress更新emoji
  • watch监视-ref对象类型数据
  • 网站建设的英语怎么做淘宝客网站做淘客
  • MBSE:数字模型重塑系统工程未来
  • 排序算法的相关讨论
  • HDFS 之 CacheAdmin
  • MySQL数据库07:分组查询与分类查询
  • 淄博网站公司高端网站建设公司怎么做推广
  • MCU的I/O防护
  • 碳纤维便携式气象站:轻量化设计,随时随地掌握气象数据
  • 华为-AI智算网络学习-2
  • K8S RD: Kubernetes从核心调度到故障排查、网络优化与日志收集指南
  • Java 项目里的那些坑
  • 【读书笔记】NVIDIA DGX
  • 岑溪网站开发工作室宁波网站制作公司费用价格
  • 揭阳智能模板建站海口百度seo公司
  • 智能SQL优化工具 PawSQL 月度更新 | 2025年10月
  • 烟台市网站建设用电脑怎么做原创视频网站
  • Total PDF Converter v6.5.0.356.0 电脑PDF多功能转换器
  • 【Android】MVVM实战:仿Launcher加载与应用过滤
  • seowhy什么意思丹阳seo公司
  • 质量智能革命:SPC软件助力中国制造驶入高质量发展快车道
  • 步骤记录器广州搜索排名优化
  • 3分钟搞定,CI/CD工具Arbess安装和配置
  • 5G智慧矿山监控终端:引领矿山智能化新潮流 在科技浪潮汹涌澎湃的当下,矿山行业正处于智能化转型的关键转折点
  • oracle 11查询数据库锁