一条 SQL 从 12 秒到 0.2 秒,我只改了一个关键字
今天给你分享一个我们在生产环境里踩过无数坑才摸透的SQL优化真实案例——问题出在滥用派生表(Derived Table),结果一张看似无害的子查询,让整个查询变成了全表扫描的噩梦。而 PawSQL 的一条重写规则,直接把性能从12秒拉到0.2秒。
先看一个真实到扎心的场景:
-- 查询每个用户最近3条订单
SELECT u.*, t.order_id, t.created_at
FROM users u,
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE u.id = t.user_id
AND t.rn <= 3;100 万订单、10 万用户,这条 SQL 跑了12.3 秒。为什么?因为派生表先对orders全表计算了窗口函数,生成一个百万级的临时结果集,然后再和users关联过滤。绝大多数计算都是浪费的—— 你只需要每个用户的 3 条记录,却先算了所有用户的所有记录!
而我们把它改写成下面这样:
SELECT u.*, t.order_id, t.created_at
FROM users u,
LATERAL (
SELECT order_id, created_at
FROM orders
WHERE user_id = u.id
ORDER BY created_at DESC
LIMIT 3
) t;执行时间骤降到0.2 秒,性能提升61 倍!
这就是 PawSQL的派生表转换为Lateral表关联(DerivedTable2LateralJoin) 算法干的事:自动识别低效派生表,智能转换为 LATERAL JOIN + 下推条件 + LIMIT。
今天,我就带你深入解析这条重写优化的算法逻辑,并给出两个可以直接抄作业的优化案例。
先看执行流程对比,一眼看懂差距:

普通派生表是 "先做满汉全席,再挑 3 道菜吃";LATERAL JOIN 是 "你想吃 3 道菜,我就只做这 3 道菜"。
但手动改写 LATERAL JOIN 有两个致命痛点:
PawSQL 的这条规则,就是帮你安全、自动地完成这个转换,让你不用再死磕 SQL 语法细节。
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM employee
) t
WHERE t.rn <= 3;SELECT d.dept_name, t.*
FROM department d,
LATERAL (
SELECT *
FROM employee
WHERE dept_id = d.id
ORDER BY salary DESC
LIMIT 3
) t;算法核心逻辑
ROW_NUMBER()或RANK()PARTITION BY字段完全等于外层关联字段=、<或<=,且右值是常量或参数SELECT列表中(防止列丢失)rn <= 3 → LIMIT 3rn < 3 → LIMIT 2rn = 1 → LIMIT 1rn = 2 → LIMIT 1 OFFSET 1完美支持参数化:rn = ? → LIMIT ?-1, 1
rn BETWEEN ? AND ?-- 查询每个用户的订单总额,只返回前10个用户
SELECT u.*, t.total
FROM users u,
(
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
) t
WHERE u.id = t.user_id
ORDER BY u.id
LIMIT 10;orders全表分组聚合(假设有 100 万用户,就产生 100 万行),然后关联、排序、取前 10。99.99% 的聚合计算是浪费的!SELECT u.*, t.total
FROM users u,
LATERAL (
SELECT SUM(amount) AS total
FROM orders
WHERE user_id = u.id
GROUP BY user_id
) t
ORDER BY u.id
LIMIT 10;严格匹配条件:
GROUP BY,且GROUP BY字段包含所有外层关联字段,=),LIMIT(否则可能破坏语义),LIMIT很多数据库(包括 MySQL 8.0 之前)的优化器不会自动将派生表转换为 LATERAL JOIN,因为:
LATERAL 语义在旧版本中不存在;PawSQL 作为外置 SQL 优化引擎,可以大胆且精细地完成这个转换,并通过严格的语义校验保证结果 100% 等价。
除了上述基本转换,我们的规则还做了这些 "贴心" 设计:
PARTITION BY与外层关联字段是否一致,防止结果错误? = rn这种反人类写法。测试环境:MySQL 8.0,100 万行 orders 表,10 万行 users 表,user_id 上有普通索引。

注意:实际加速比取决于数据分布和索引。如果 user_id 上有覆盖索引,LATERAL 版本几乎只扫描需要的行,性能还能再提升一个数量级!
DerivedTable2LateralJoinRewrite已启用(默认开启)当然,你也可以手动按照本文的案例模式改写,但请务必注意:
PARTITION BY是否与关联字段完全一致ORDER BY能正确对应到ORDER BY ... LIMIT交给 PawSQL,省心又安全。
派生表转 LATERAL JOIN,是 SQL 优化中 "性价比" 极高的手法。它能把 O (N²) 的复杂度降为 O (N * K),其中 K 是每个分组需要的记录数。
PawSQL 将这一复杂改写过程自动化、智能化,让开发者不再需要手动分析窗口函数、推导 LIMIT、下推条件。你只管写业务 SQL,优化交给我们。
目前,全球已有超过20,000 名专业数据库从业者在使用 PawSQL 的服务,支持 MySQL、PostgreSQL、Oracle、SQL Server 等20 + 种主流数据库。除了查询重写,我们还提供智能索引推荐、自动化性能验证、数据库性能巡检等一站式数据库性能优化解决方案。