最稳妥做法是用row_number()配合partition by transaction_id和order by update_time desc、id desc,在子查询或cte中编号后外层筛选rn=1;where条件须分内外两层,且需建立(transaction_id, update_time desc)联合索引。

用 ROW_NUMBER() 获取每笔交易最新状态
直接在子查询或 CTE 中对交易记录按 transaction_id 分组、按时间倒序编号,再筛出 rn = 1 的行——这是最稳妥的做法。别指望靠 ORDER BY ... DESC 加 LIMIT 1 一查了事,那只能拿到全局最新一条,不是“每笔交易各自的最新状态”。
ROW_NUMBER() 必须配合 PARTITION BY 使用
如果漏写 PARTITION BY transaction_id,ROW_NUMBER() 就会把整张表当一个组编号,结果全是 1 到 N,失去“每笔交易内排序”的意义。正确写法是:
ROW_NUMBER() OVER ( PARTITION BY transaction_id ORDER BY update_time DESC, id DESC )
注意两点:
-
update_time是状态更新时间字段,优先按它倒序;如果存在同一秒多条更新,补上id DESC防止排序不稳定 - 不同数据库对
NULL值默认排序方向不一致(如 PostgreSQL 默认NULLS LAST,MySQL 无此语法),若update_time可能为空,显式加NULLS LAST(PostgreSQL)或先过滤(MySQL/SQL Server)更安全
WHERE 条件必须放在外层,不能写在窗口函数里
常见错误是试图在 OVER() 里加 WHERE 或用 FILTER(仅 PostgreSQL 支持且不适用于此场景)。窗口函数本身不支持条件过滤分组内行——要筛数据,得套一层:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY transaction_id
ORDER BY update_time DESC
) AS rn
FROM transaction_status
WHERE status IN ('success', 'failed', 'pending') -- ✅ 这里可加状态过滤
) t
WHERE rn = 1; -- ✅ 筛每笔交易的最新一条
关键点:
- 内层
WHERE控制参与排序的原始数据范围(比如只看有效状态) - 外层
WHERE rn = 1才是取“最新”,顺序不能颠倒 - 如果在内层漏掉某笔交易的所有记录(比如全为
unknown状态),它就不会出现在最终结果里
性能敏感时,务必给排序字段建联合索引
执行计划里如果看到 WindowAgg 节点耗时高,大概率是没走索引。针对上面的写法,最优索引是:
CREATE INDEX idx_txn_time ON transaction_status (transaction_id, update_time DESC);
原因:
-
PARTITION BY字段必须放索引最左列,否则无法高效分组 -
ORDER BY字段紧随其后,并明确声明DESC(部分数据库如 PostgreSQL 对索引方向敏感) - 如果查询还带其他
WHERE条件(如created_at > '2024-01-01'),需评估是否扩展为三列索引,但优先保前两列
没有这个索引,数据量一过十万,ROW_NUMBER() 就可能从毫秒级拖到秒级——而且问题往往在压测时才暴露。











