SQL查询优化实战:4个让查询快10倍的写法"

admin 2026-10-21 20:13:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

SQL 查询优化实战:4 个让查询快 10 倍的写法

钟智强
钟智强

哪吒网络安全

2026年7月6日 13:13
马来西亚

在小说阅读器读本章

去阅读

—— 从「能跑」到「跑得快」,一线数据开发最常踩的 4 个坑

分类:数据库 / SQL 优化    标签:SQL、MySQL、CTE、索引、执行计划    阅读时长:约 10 分钟

前言

同样一句 SQL,返回的结果一模一样,但一条 50 毫秒返回,另一条要跑 30 秒 —— 这在生产环境中太常见了。正如那句被反复引用的话:Not all SQL queries are made equal(并非所有 SQL 查询都生而平等)。

很多同学写 SQL 只关心「结果对不对」,却忽略了「数据库到底做了多少无用功」。本文围绕四个最高频、也最容易被忽视的写法问题展开,每一条都给出反例、正例、原理图解和注意事项,帮助你把查询从「能跑」优化到「跑得快」。这四点分别是:

●列的选择:用 SELECT \* 还是只取需要的列;

●过滤时机:在 JOIN 之前过滤,还是 JOIN 之后过滤;

●重复计算:用相关子查询逐行计算,还是用 CTE 计算一次并复用;

●索引使用:过滤条件落在无索引列上,还是落在索引列上。

一、别再用 SELECT \*:只取你需要的列

问题场景

SELECT \* 写起来最省事,但它会让数据库读取并返回表中的每一列 —— 包括那些你根本用不到的大字段(如长文本、JSON、BLOB)。这带来三重成本:更大的磁盘 I/O、更多的网络传输、以及无法命中「覆盖索引」。

× 低效写法

SELECT \* FROM table\_name;

√ 推荐写法

SELECT column1, column2, column3 FROM table\_name;

图 1:SELECT \* 读取全部列,而按需选择只读取真正用到的列

为什么更快

覆盖索引(Covering Index)是关键收益点。如果一个索引已经包含了查询所需的全部列,数据库可以直接从索引里返回数据,完全不用回表(回到主键聚簇索引再取一次行)。一旦你写了 SELECT \*,只要表里有任何一列不在该索引中,覆盖索引就失效了,查询被迫回表,性能骤降。

此外,返回的列越少,结果集在内存、排序缓冲区、网络管道中占用的空间就越小,对高并发场景尤其明显。

注意事项 • 视图、ORM 或 BI 工具里隐藏的 SELECT \ 同样有代价,排查慢查询时别放过; • 表结构变更(新增大字段)会让原本没问题的 SELECT \ 突然变慢; • 只在临时排查、确实需要全部列时才用 \* ,生产代码应显式列出字段。

二、过滤时机:在 JOIN 之前过滤,而不是之后

问题场景

当你先把两张大表 JOIN 起来、再用 WHERE 过滤时,数据库往往要先生成一个庞大的中间结果集,然后才把绝大部分行丢掉。真正需要的可能只有几千行,却让上百万行参与了连接运算 —— 这是巨大的浪费。

× 低效写法:先 JOIN 再 WHERE

SELECT t1.column\_1, t2.column\_2 FROM table1 t1 JOIN table2 t2 ON t1.foreign\_id = t2.primary\_id WHERE t2.filter\_column = 'some\_value';

√ 推荐写法:用 CTE 在 JOIN 之前过滤

WITH cte AS (     SELECT \*     FROM table2     WHERE filter\_column = 'some\_value' ) SELECT t1.column\_1, cte.column\_2 FROM table1 t1 JOIN cte ON t1.foreign\_id = cte.primary\_id;

图 2:先在 CTE 中把 table2 过滤到很小,再参与 JOIN,中间结果大幅缩小

为什么更快

核心思想是「尽早缩小数据量」(predicate pushdown,谓词下推)。先把过滤条件应用到单表上,让参与连接的行数从百万级降到千级,连接的代价随之呈数量级下降,占用的内存和排序开销也更小。

注意事项 • 现代优化器(如 MySQL 8.0、PostgreSQL)在很多情况下会自动下推谓词,此时两种写法执行计划相同; • 但当涉及 OUTER JOIN、复杂子查询或视图时,优化器不一定能自动下推,显式用 CTE / 派生表更稳妥; • 别把过滤条件误写在 OUTER JOIN 的 ON 上,那会改变语义(左表不匹配行会被保留)。

三、用 CTE 替代重复的相关子查询

问题场景

在 SELECT 列表里塞入多个子查询(尤其是相关子查询)时,这些子查询可能会对外层结果的每一行都重新执行一次。下面的例子对同一份 JOIN 结果分别求 MAX 和 MIN,等于把相同的扫描做了两遍甚至更多遍。

× 低效写法:多个子查询重复扫描

SELECT column\_1,     (SELECT MAX(column2) FROM table1 t1      JOIN table2 t2 ON t1.column3 = t2.column3) AS max\_value,     (SELECT MIN(column2) FROM table1 t1      JOIN table2 t2 ON t1.column3 = t2.column3) AS min\_value FROM table3;

√ 推荐写法:CTE 只计算一次并复用

WITH cte AS (     SELECT MAX(column2) AS max\_value,            MIN(column2) AS min\_value     FROM table1 t1     JOIN table2 t2 ON t1.column3 = t2.column3 ) SELECT column\_1, cte.max\_value, cte.min\_value FROM table3, cte;

图 3:相关子查询逐行重复扫描;CTE 把聚合计算一次,然后被所有行复用

为什么更快

把公共的、重复的计算抽取到一个 CTE(Common Table Expression,公共表表达式)中,数据库只需扫描并聚合一次,得到 max\_value 和 min\_value 两个标量值,再让外层查询直接引用。原来「N 行 × 多次扫描」的开销被压缩为「一次扫描」,可读性也明显更好。

注意事项 • CTE 主要提升可读性与可维护性,是否物化(materialize)取决于数据库;例如 PostgreSQL 12+ 默认可内联,MySQL 会视情况物化; • 若确实需要缓存中间结果,可考虑临时表或数据库特定的物化提示; • 示例中 FROM table3, cte 是笛卡尔积写法:因为 cte 只有一行标量,等价于给每行附加两列,但业务中使用交叉连接务必确认行数。

四、让过滤条件落在索引列上

问题场景

WHERE 条件如果作用在没有索引的列上,数据库只能逐行扫描整张表(全表扫描),复杂度是 O(N)。把过滤条件放到已建索引的列上,数据库就能借助 B-Tree 索引快速定位,复杂度降到 O(log N),数据量越大,两者差距越夸张。

× 低效写法:过滤无索引列

SELECT column\_1 FROM table\_name WHERE unindexed\_column = 'some\_value';

√ 推荐写法:过滤索引列

SELECT column\_1 FROM table\_name WHERE indexed\_column = 'some\_value';

图 4:无索引 → 逐行全表扫描 O(N);有索引 → B-Tree 定位 O(log N)

为什么更快

B-Tree 索引是有序的多叉平衡树,查找时从根节点出发,每一层都能排除掉大部分数据,只需少数几跳就能定位到目标。这就是「逐行找」和「跳着找」的区别。

让索引失效的常见写法(务必避开) • 在索引列上套函数:WHERE DATE(created\_at) = '2026-07-06' → 改写成范围条件; • 前置通配符模糊匹配:WHERE name LIKE '%abc' → 无法利用 B-Tree,'abc%' 才可以; • 隐式类型转换:索引列是字符串却传入数字,会触发全表扫描; • 对索引列做运算:WHERE price \* 2 > 100 → 改成 WHERE price > 50; • 索引不是越多越好:写入时要维护索引,过多索引拖慢 INSERT / UPDATE。

五、四条原则速查

图 5:四条 SQL 优化原则一图速查

六、如何验证优化是否生效:EXPLAIN

优化不能靠感觉,要用执行计划说话。绝大多数数据库都提供 EXPLAIN(或 EXPLAIN ANALYZE)来查看查询的执行方式:

-- MySQL / PostgreSQL EXPLAIN ANALYZE SELECT column1, column2 FROM table\_name WHERE indexed\_column = 'some\_value';

看执行计划时重点关注这几个信号:

●是否全表扫描:MySQL 里 type=ALL、PostgreSQL 里 Seq Scan 通常意味着没用上索引;

●是否命中索引:关注 key / Index Scan / Index Only Scan,后者代表命中了覆盖索引;

●预估与实际行数:rows 估计值和 actual rows 差距过大,往往意味着统计信息过期,需要 ANALYZE;

●是否有额外开销:Using filesort、Using temporary 提示存在排序或临时表,可能需要补索引。

结语

这四条原则本质上都指向同一个思想:让数据库少做无用功 —— 少读列、少连接行、少重复计算、少扫描。它们不依赖任何高级特性,却能在真实业务里带来数量级的性能提升。

当然,优化没有银弹。是否生效、生效多少,最终都要以你所用数据库的执行计划为准。建议养成写完 SQL 顺手 EXPLAIN 一下的习惯,用数据驱动优化,而不是凭直觉。

如果这篇文章对你有帮助,欢迎点赞、收藏、评论交流你踩过的 SQL 性能坑。


SQL查询优化实战:4个让查询快10倍的写法" 网络安全文章

SQL查询优化实战:4个让查询快10倍的写法"

本文总结了提升SQL性能的四个核心写法:避免SELECT\\*以利用覆盖索引减少回表;JOIN前用CTE下推谓词缩小数据量;用CTE替代重复子查询避免逐行计算;确保过滤条件命中索引列,避开函数和左模糊等导致索引失效的写法。建议通过EXPLAIN执行计划验证优化效果。
WinPE:作为Windows驱动测试与模糊测试的无状态测试框架" 网络安全文章

WinPE:作为Windows驱动测试与模糊测试的无状态测试框架"

本文介绍如何将WindowsPE改造为轻量级、幂等的测试框架,用于KMDF驱动测试与模糊测试。核心方案包括:通过BCD配置优化引导速度、禁用VBS/HVCI以加载未签名驱动、使用QEMUi440FX芯片组确保硬件可预测性、配置KDNET网络调试并避免Hyper-Venlightenments冲突。通过替换系统外壳为测试代理并利用WinPE重启机制,可实现自动化测试循环。该方案显著降低资源开销并提高测试确定性。
给黑客的jq指南" 网络安全文章

给黑客的jq指南"

本文面向黑客和系统管理员,介绍了JSON处理工具jq的核心用法。文章从基础概念讲起,涵盖美化输出、提取特定字段、使用select过滤数据等操作,并通过解析ldapdomaindump输出等实战示例展示其应用。作者强调掌握点号引用、数组遍历、select过滤等心智模型即可应对大部分场景,鼓励读者放弃传统CSV工具,拥抱JSON。
评论:0   参与:  0