子查询里的 ORDER BY 与 LIMIT
子查询里想表达「排序后取前 N 行」,ORDER BY 必须与 LIMIT 搭配才有确定语义:只写 ORDER BY 而不写 LIMIT,优化器可以忽略这个排序,外层查询的顺序也不受它保证。标量子查询则更进一步——它必须最多返回一行,否则运行时报错,所以「取最新一条」要写成 ORDER BY … LIMIT 1,并且拿主键做次级排序键,避免时间相同的行顺序不确定。外层行数大时应改用窗口函数或 LATERAL。
基本信息
| 语义规则 | ORDER BY 与 LIMIT 搭配时才有确定的「前 N 行」 |
|---|---|
| 标量子查询 | 必须保证最多返回一行,多行会直接报错 |
| 排序稳定性 | 排序值可能重复时追加主键做次级排序键 |
| 性能特征 | 标量子查询逻辑上逐行执行,外层行数乘以执行次数 |
| 替代写法 | 窗口函数 ROW_NUMBER、LATERAL、先聚合再连接 |
典型使用场景
- 每行取最近一条订单的金额或状态
- 每组取最新状态用于列表展示
- 先取 Top N 子集,再与主表连接取详细信息
- 过滤后取第一条用于判空与兜底
- 排查「子查询返回多行」导致的线上报错
标量子查询取一条
标量子查询取一条
SELECT u.id, u.name,
(SELECT o.amount
FROM orders o
WHERE o.user_id = u.id
ORDER BY o.created_at DESC, o.id DESC # 主键兜底,顺序确定
LIMIT 1) AS last_amount
FROM users u
WHERE u.id <= 50;
-- 只写 ORDER BY 不写 LIMIT:排序语义不被保证,外层也不继承
SELECT id FROM (SELECT id FROM orders ORDER BY created_at DESC) t;几种写法对比
| 需求 | 写法 | 注意点 |
|---|---|---|
| 取一条标量值 | 子查询 + ORDER BY + LIMIT 1 | 必须保证顺序唯一,多行会报错 |
| 每用户取最新一行 | 窗口函数 ROW_NUMBER | 一次扫描完成,比逐行子查询快 |
| 每行取一行还要连接 | LATERAL 子查询 | 内层必须走索引,注意 MySQL 版本 |
| 先取前 N 行再连接 | 子查询 + LIMIT 后连接 | 范围明确,便于优化器处理 |
子查询里的 ORDER BY 只有与 LIMIT 搭配时才有实际意义;外层语句也不会继承子查询的顺序,需要确定输出顺序就在最外层写 ORDER BY。
标量子查询返回多行会直接报错(MySQL 1242、PostgreSQL 的 more than one row returned by a subquery);上线前必须用真实数据验证「最多一行」这个前提成立。