首页 数据库 一则复杂 SQL 改写后有感

一则复杂 SQL 改写后有感

抱歉很久没写技术博客。
自从LLM爆发之后,写概念堆叠的所谓“博客”已经没有意义了,那么我会思考我的博客还有什么作用。

得出的结论是:具体业务的讨论仍然是有价值的

所以之后会随缘更新一些强业务相关的博客

为什么说随缘呢?因为这段时间经历了一些事情,让我感觉比起死磕技术(工作),体验生活才是我的首要目标

别说什么裁员的,真裁员我还来不及高兴呢

以上,2025.06.12

背景:维护中遇到一段“屎山 SQL”
近期在优化一个业务接口性能时,发现其背后依赖了一段极为复杂的 SQL 查询,执行时间长达 20 多秒。这段 SQL 看似在统计一些业务数据,但结构混乱、嵌套严重、含义难懂,最严重的是,其中一段使用了过度复杂的窗口函数嵌套,成为性能瓶颈。

本文记录我分析、重构并优化该 SQL 的过程,并结合实际经验,聊聊 SQL 优化中“理解数据关系 vs 语法技巧”的重要性。

原始 SQL 功能分析
该 SQL 由三部分构成,通过 UNION ALL 拼接。表结构如下:

sdd_t_ace: 主表,记录交易信息。字段有 scan_seq_no, tr_date, status, b_code 等。
aaa_t_tr_over_log: 记录交易“处理完成”的日志。字段有 scan_seq_no, finished_time
aaa_t_tr_obtain_log: 记录交易“领取处理”的日志。字段有 ace_id, tr_date
SQL 三段查询的业务目标(推测)
太难看了只能靠猜

序号 SQL 功能 推测业务目标
① 查询今日内所有交易记录数量 统计交易总量
② 查询今日内未完成的交易记录 统计待处理数量
③ 查询今日内已完成但处理时间晚于某时间点,且已被领取的任务 检查延迟处理的任务
第三段 SQL(屎山核心)原样示例(已简化)
select scan_seq_no, finished_time, obtain_time
from (
select scan_seq_no, finished_time
from (
select t1.scan_seq_no, t2.finished_time,
row_number() over(partition by t2.scan_seq_no order by t2.finished_time desc) as rank1
from sdd_t_ace t1
left join aaa_t_tr_over_log t2 on t1.scan_seq_no = t2.scan_seq_no
where t1.status != 0
and t1.tr_date between curdate() and '2025-06-06 16:30'
) k1
where finished_time >= '2025-06-06 16:30' and rank1 = 1
) m1
left join aaa_t_tr_obtain_log m2 on m1.scan_seq_no = m2.scan_seq_no
where m2.tr_date <= '2025-06-06 16:30'
存在的问题分析
问题点 描述 技术影响
多层嵌套 三层 SELECT 嵌套,逻辑难以理解 执行计划难优化,难排查
重复排序 两次 row_number() 排序 触发多次 filesort,严重拖慢性能
LEFT JOIN + WHERE 使用 LEFT JOIN 却又筛选右表字段 实际等同 INNER JOIN,但影响优化器判断
字段使用混乱 m1.scan_seq_no = m2.scan_seq_no 实为业务等价字段,但未解释 增加阅读成本
重写目标:保证结果一致,逻辑清晰,性能提升
优化后的 SQL(最终版本),如下:

select substring(b_code, 1, 3) as b_code_per3, count(*) as dealTask_cnt
from (
select distinct sdd.scan_seq_no, sdd.b_code
from sdd_t_ace sdd
inner join aaa_t_tr_over_log log on sdd.scan_seq_no = log.scan_seq_no
inner join aaa_t_tr_obtain_log m2 on sdd.scan_seq_no = m2.ace_id
where sdd.status != 0
and log.finished_time >= '2025-06-06 16:30'
and sdd.tr_date between curdate() and '2025-06-06 16:30'
and m2.tr_date <= '2025-06-06 16:30'
) grouped
group by substring(b_code, 1, 3)
优化思路解析
1. 用 distinct 替代 row_number()
原意是“每个 scan_seq_no 取最新的一条”,但如果我们能提前确认一条记录只会对应唯一一条日志,则无需 row_number,使用 distinct 更高效。

2. 使用 INNER JOIN 替代 LEFT JOIN + WHERE
原始代码中 LEFT JOIN ... WHERE m2.tr_date <= ... 实际相当于 INNER JOIN,直接明确意图。

3. 消除嵌套子查询
去掉所有 SELECT FROM (SELECT FROM (SELECT ...)) 的结构,提升可读性,也让优化器更好地生成执行计划。

4. 预判数据范围 + 合理过滤条件
结合业务逻辑判断只需要查 today 到 目标时间 的数据,避免全表扫描。

实际执行效果对比
比较项 原始 SQL 优化后 SQL
查询耗时 约 20 秒 < 1 秒
可读性 嵌套复杂,字段混乱 简洁清晰,一目了然
可维护性 差,修改易出错 高,字段含义明确
技术反思:SQL 优化靠什么?
错误理解:“优化 SQL 就是写法要帅,函数要高级”
这种想法容易导致用 row_number、CTE、嵌套写法堆叠逻辑,表面花哨,实则低效。

正确理解:SQL 优化 80% 是理解数据,20% 是写法技巧
能力 描述
理解表关系 哪些字段是一对一?一对多?是外键?
理解字段语义 字段如 status, finished_time,分别代表什么状态?何时更新?
理解数据分布 status=0 的比例?b_code 的稀疏度?
清楚业务目标 查询是统计量?查异常?查明细?
一些思考
SQL 优化怎么问、怎么做?
面对陌生 SQL 的优化流程:
理解业务意图:这条 SQL 是做什么的?统计?监控?导出?
理清字段含义:问清楚字段和表的关系,不怕问错,只怕想当然。
观察数据量和索引:EXPLAIN + SHOW INDEX 是必须工具。
写小查询试水:验证表连接关系和过滤逻辑,减少误解。
一步步重构:每改一段先测一次,再合并。
总结
真正复杂的 SQL 往往不是难在语法,而是难在“它试图一口气表达太多复杂业务意图”。

而优化 SQL 的关键,并不是你能写多炫技的语句,而是你是否能——

看懂它背后真正想干什么;
拆解出最小单元;
找到正确字段连接路径;
用最朴素的方式表达业务意图。

站心网

抱歉很久没写技术博客。自从LLM爆发之后,写概念堆叠的所谓“博客”已经没有意义了,那么我会思考我的博客..

为您推荐

微软 SQL Server 2025 全新特性解析 | AI 加持、向量搜索、T‑SQL 革新

AI 原生嵌入:Copilot & 向量功能Copilot 集成 SSMS 21:在 SQL Server Management Studio 中添加 AI 助手,支持自然语言生成和优化 T‑SQL 查询。向量数据类型与索引:新增原生向量字段(支持单精度浮点),并提供..

SQL Server 创建表报错:'OPTIMIZE_FOR_SEQUENTIAL_KEY' is not a recognized CREATE TABLE option.

这个错误 'OPTIMIZE_FOR_SEQUENTIAL_KEY' is not a recognized CREATE TABLE option. 表示你的目标 SQL Server 版本过旧,不支持这个选项。OPTIMIZE_FOR_SEQUENTIAL_KEY 是 SQL Server 2019(及 Azure SQL) 引入的..

Login failed for user '<token-identified principal>'. (Microsoft SQL Server, Error: 18456) 报错解决方法

登录SQL Server数据库时报错:TITLE: Connect to Server------------------------------Cannot connect to <Server address>.------------------------------ADDITIONAL INFORMATION:Login failed for user '<token-..

SQL Server 中存储 GUID:选择 uniqueidentifier 还是 nvarchar?最佳实践解析

在 SQL Server 中存储 GUID 时,选择 uniqueidentifier 还是 nvarchar?本文深入分析两者的差异,探讨性能、存储和索引的影响,提供最佳实践建议,帮助你优化数据库设计。1. uniqueidentifier 与 nvarchar 的基本区..

数据库SQL优化大总结之 百万级数据库优化方案

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from..

为什么说 LINQ 要胜过 SQL

如果你还没有沉溺于 LINQ,就会想这有啥大惊小怪的。SQL 并没有坏掉,为什么还要对它进行修补呢? 为什么我们还需要另外一种查询语言呢?流行的说法是 LINQ 同C#(或者 VB)集成在了一起,故而消除了编程语言和数据库..

发表回复

返回顶部

微信分享

微信分享二维码

扫描二维码分享到微信或朋友圈

链接已复制