MySQL 8.0+ 应优先使用 ROW_NUMBER() 窗口函数,语义清晰、结果确定;必须搭配 OVER(ORDER BY ...),不支持无序使用;旧版 MySQL 5.7 需用变量方案但需注意初始化、排序和兼容性问题。

MySQL 8.0+ 直接用 ROW_NUMBER() 最稳
有窗口函数就别绕弯子。MySQL 8.0 及以上版本原生支持 ROW_NUMBER(),语义清晰、结果确定、无需维护变量状态。
常见错误是套用旧版写法(比如 @row := @row + 1)却没加 ORDER BY,导致行号顺序随机——窗口函数天然绑定排序逻辑,不会踩这个坑。
- 必须搭配
OVER(ORDER BY ...)使用,否则报错:Window 'w' requires an ORDER BY clause - 如果只想要全局序号(不按业务字段排),就写
OVER(ORDER BY (SELECT NULL)),但注意:这不保证物理顺序,仅满足语法 - 性能上比用户变量方案更可靠,优化器能更好处理窗口函数执行计划
SELECT ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn, id, name FROM users WHERE status = 'active';
MySQL 5.7 或更低版本只能靠用户变量
变量方案本质是“伪自增”,依赖执行顺序,而 MySQL 不保证 SELECT 中表达式求值顺序——所以必须用派生表或 ORDER BY 强制稳定。
最容易出问题的是把变量初始化和查询写在同一语句里,比如 SELECT @row := @row + 1, ... FROM t, (SELECT @row := 0) r,在某些版本或开启 sql_mode=STRICT_TRANS_TABLES 时会失效。
- 安全写法:先单独执行
SET @row := 0,再查;或用子查询包裹变量初始化 - 必须在主查询中包含
ORDER BY,否则@row增加顺序和结果行顺序不一致 - 不能在含有
GROUP BY或聚合函数的查询中混用,变量行为不可预测
SELECT @row := @row + 1 AS rn, id, name FROM users, (SELECT @row := 0) r WHERE status = 'active' ORDER BY created_at DESC;
ROW_NUMBER() 和 RANK()、DENSE_RANK() 别混用
三者都生成序号,但处理并列的方式完全不同,选错会导致业务逻辑出错。
比如两个用户同为最高分,ROW_NUMBER() 给 1 和 2,RANK() 给 1 和 1 后跳到 3,DENSE_RANK() 给 1 和 1 后直接是 2——报表分页、排行榜名次、TOP N 过滤都依赖这个差异。
- 要严格递增且唯一编号(如分页序号),只用
ROW_NUMBER() - 做排名展示且允许并列(如“并列第一”),看需求选
RANK()或DENSE_RANK() - 所有窗口函数都要求
OVER子句,漏写会报语法错误:ERROR 1064: You have an error in your SQL syntax
PostgreSQL / SQL Server 怎么办
这些数据库早就有标准窗口函数支持,用法和 MySQL 8.0 一致,不用折腾变量。
但要注意 PostgreSQL 对空排序键的处理更严格:如果 ORDER BY 字段含 NULL,默认排在最前,可能影响行号起始位置;SQL Server 则需确认兼容级别 ≥ 120(即 SQL Server 2012+)。
- PostgreSQL 示例:
SELECT ROW_NUMBER() OVER (ORDER BY score DESC NULLS LAST), name FROM players - SQL Server 示例:
SELECT ROW_NUMBER() OVER (ORDER BY login_time ASC), user_id FROM logins - 跨数据库迁移时,
ROW_NUMBER()是最可移植的方案;用户变量是 MySQL 特有黑魔法,其他库根本不认
ORDER BY 缺失、并列语义混淆——这三个点,只要一个没对齐,行号就不是你想要的那串数字。

















