SQL改写实战:子查询、CTE、窗口函数性能对比

本文涉及的产品
RDS AI 助手,专业版
PolarDB Agent Express,2核4GB
PolarDB Agent Flow,2核4GB
简介: 本文聚焦SQL性能优化,实测对比子查询、CTE与窗口函数在复杂统计、分组排名、递归查询等场景的执行效率。基于MySQL 8.0真实数据(千万级表),揭示窗口函数在“每组取最值”“部门排名”中提速3倍以上,CTE提升可读性与递归能力,而相关子查询易成性能瓶颈。干货满满,避坑必备!

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

写SQL写久了就会发现,同样的业务需求,不同的人写出来,执行效率可能差几十倍。尤其是在复杂统计、排名分组这些场景,选错写法,查询能从毫秒级变成分钟级。以前做运营的时候,我只需要看懂Excel里的公式;转行后才明白,SQL优化的本质是“用更少的扫描、更少的临时表完成同样的事”。今天就把子查询、CTE、窗口函数这三者的性能差异彻底讲透。

这两年MySQL 8.0普及之后,CTE和窗口函数不再是“新特性”,而是每个写SQL的人必须掌握的技能。很多以前只能用子查询或临时表硬扛的需求,现在有了更优雅高效的解法。理解这三种写法的性能特性,能让你在面对复杂报表时少走弯路。

先花一分钟搞懂三个概念

  • 子查询​:把一个查询的结果作为另一个查询的条件或数据源。优点是直观,缺点是相关子查询(外层每行执行一次内层)性能极差。
  • CTE(公共表表达式)​:可以理解为一个命名的临时结果集,只在当前查询中有效。提升可读性,还能支持递归查询(比如组织架构树)。
  • 窗口函数​:在保留原行数据的基础上,对一组行进行聚合或排名计算。不会像GROUP BY那样压缩行数,非常适合“每行后面加一个汇总值”的场景。

实测对比:每种写法最适合什么场景

场景 推荐写法 原因
简单过滤(查某个用户的最新订单) 窗口函数 或 CTE+JOIN 一次扫描完成,效率高
多步复杂逻辑,需要分步写 CTE 可读性最好,便于调试
每行后面跟一个汇总值 窗口函数 不改变行数,一次扫描
递归查询(树形、BOM) 递归CTE 唯一可行的标准写法
EXISTS / IN 半连接子查询 优化器能处理好
相关子查询 改写成JOIN或窗口函数 相关子查询通常较慢

真实数据:1000万行表,查每个客户的最新订单

环境:MySQL 8.0.32,8C32G,表orders有索引(order_date, customer_id)

❌ ​相关子查询​:耗时12.5秒

SELECT * FROM orders o1 
WHERE order_date = (SELECT MAX(order_date) FROM orders o2 WHERE o2.customer_id = o1.customer_id);

✅ ​窗口函数​:耗时3.8秒

SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) as rn
    FROM orders
) t WHERE rn = 1;

✅ ​CTE+JOIN​:耗时4.2秒

WITH latest AS (
    SELECT customer_id, MAX(order_date) as max_date
    FROM orders GROUP BY customer_id
)
SELECT o.* FROM orders o JOIN latest l ON o.customer_id = l.customer_id AND o.order_date = l.max_date;

结论:相关子查询最慢,窗口函数和CTE+JOIN都在4秒左右。以后遇到“每组取最值”的需求,优先用窗口函数。

排名统计场景:按部门计算员工工资排名

10万行员工表,按部门内工资排名。

窗口函数(RANK):0.9秒,代码一行

SELECT *, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) as rnk FROM emp;

自连接旧写法:7.2秒,SQL复杂难懂

SELECT e1.*, COUNT(DISTINCT e2.salary) as rnk
FROM emp e1 LEFT JOIN emp e2 ON e1.dept_id = e2.dept_id AND e1.salary <= e2.salary
GROUP BY e1.id;

窗口函数在排名、累计、移动平均等场景下,性能和简洁度都是碾压级别。

递归CTE实际运用:查询组织架构树

需求:部门表dept(id, parent_id, name),查询id=1及其所有下级。

WITH RECURSIVE dept_tree AS (
    SELECT id, parent_id, name FROM dept WHERE id = 1
    UNION ALL
    SELECT d.id, d.parent_id, d.name FROM dept d
    INNER JOIN dept_tree dt ON d.parent_id = dt.id
)
SELECT * FROM dept_tree;

一次查询搞定,这是MySQL 8.0之前很难优雅实现的。

一点体会

从运营转行做DBA之后,我最深的感受是:写SQL和做运营数据分析本质上都是“从数据里找答案”,但SQL优化要求你更懂底层机制。窗口函数和CTE不是新语法,它们是工具包里最趁手的两把扳手。遇到复杂统计,第一反应应该是“能不能用窗口函数”,而不是“先写个子查询凑合用”。掌握这三者,不是为了炫技,而是让查询跑得更快、代码更容易维护。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

相关文章
|
5天前
|
存储 关系型数据库 MySQL
MySQL索引底层原理:B+树能存多少数据?页分裂与回表机制详解
数据库小学妹带你深入B+树底层:为何选它而非二叉树或哈希?揭秘页分裂/合并机制、聚簇与二级索引差异、回表代价及磁盘I/O优化逻辑。3层B+树可存约800万数据,查询仅需3次I/O!
|
29天前
|
JSON 关系型数据库 MySQL
MySQL 8.0这几个功能太实用了!5分钟帮你省下70%的代码量
MySQL 8.0重磅升级,实操利器全面登场:CTE简化嵌套与递归查询,JSON_TABLE直解析JSON为表,窗口函数赋能高效分析,不可见索引提供删除“后悔药”,强化密码策略保障企业安全——性能、安全、开发效率三重跃升。
|
5天前
|
Prometheus 监控 Cloud Native
MySQL 性能监控实战:从零搭建 Prometheus + Grafana 监控告警体系(附排查 SOP)
数据库小学妹带你从零学监控!本文详解MySQL五大核心指标维度(资源、连接、查询、InnoDB、主从),手把手配置PMM/Prometheus+Grafana监控栈,设置关键告警规则,并提供SQL快照脚本与三步排障SOP。新手友好,即装即用,让性能问题无所遁形!
|
28天前
|
关系型数据库 MySQL 测试技术
JOIN、IN、EXISTS谁最快?实测三种写法性能差异与执行计划深度剖析
本文用MySQL 8.0实测拆解`IN`/`EXISTS`/`JOIN`子查询性能:从执行计划、半连接优化、临时表开销等底层原理出发,结合10万+100万数据实测(`EXISTS`最快95ms),给出三条选型铁律——告别盲从“最佳实践”,只选最适配业务与数据的写法!
|
28天前
|
SQL 关系型数据库 MySQL
MySQL慢查询诊断实战:从10秒到0.1秒,我的5步排障法
数据库小学妹分享慢查询优化实战:从10秒降至0.08秒!详解「发现→收集→分析→优化→验证」5步排障法,覆盖慢日志配置、EXPLAIN进阶、索引失效场景、JOIN与分页优化等核心技巧,附真实案例与速查表。
|
1月前
|
SQL 关系型数据库 MySQL
批量操作性能飙升:从30秒到1秒的三种实战方法
业务系统中经常需要批量导入或更新大量数据(如Excel上传、定时同步)。许多开发人员采用循环单条执行的方式,导致1万条数据耗时30秒以上,严重影响用户体验。本文从数据库IO、事务开销、锁竞争三个角度分析单条操作的性能瓶颈,并给出三种优化方案:批量INSERT、LOAD DATA文件导入、批量UPDATE用临时表。每种方案均附实测数据对比与适用场景说明,帮助读者在1万\~100万行级别批量操作中选择最优策略。
|
5天前
|
安全 Java 数据建模
【Java基础】JDK17:密封类、模式匹配、Record类(附《思维导图》+《面试高频考点清单》)
Java 17作为LTS版本,重磅引入密封类、模式匹配与Record类三大特性:Record简化不可变数据建模,密封类精准控制继承边界,模式匹配(instanceof+switch)提升类型安全与代码简洁性。三者协同可优雅实现代数数据类型,标志着Java迈向更安全、简洁、表达力更强的现代编程语言。
【Java基础】JDK17:密封类、模式匹配、Record类(附《思维导图》+《面试高频考点清单》)
|
1月前
|
SQL 运维 关系型数据库
DBA必备技能:MySQL误删恢复完全指南(全量备份+binlog回放)
本文详解误删数据(如`DELETE FROM orders`)后的紧急恢复三步法:查Binlog→临时库回放→差异导回,并附4条血泪预防措施。不讲段子,只教能救命的操作!
|
5天前
|
SQL 人工智能 关系型数据库
DBA的AI助手:向量检索与NL2SQL入门
本篇为DBA量身打造的AI入门指南:用最直白语言讲清向量检索(相似搜索、pgvector实战)与NL2SQL(自然语言写SQL)的本质、场景及落地路径。不卷算法,只讲DBA真正需要懂的数据库新能力——技术迭代快,但掌握关键点,你依然不可替代。
|
5天前
|
运维 关系型数据库 分布式数据库
alibabacloud-polardb-ai-assistant:让大模型成为你的数据库运维搭档
PolarDB AI助手是阿里云推出的智能运维插件,将专家经验与大模型能力结合,支持MySQL/PostgreSQL。通过自然语言交互,提供性能诊断、故障排查、参数优化等20+运维能力,严格只读不写,安全可控。
169 4