ROW_NUMBER()是解决孤岛与间隙问题最直接手段,因其生成严格连续序号,确保序号差值恒定以识别孤岛;RANK()和DENSE_RANK()因跳号或并列破坏差值稳定性,不适用。

窗口函数是解决孤岛与间隙问题最直接、最可控的手段,不需要自连接或变量模拟,一行 ROW_NUMBER() 就能锚定连续性。
为什么用 ROW_NUMBER() 而不是 RANK() 或 DENSE_RANK()
孤岛识别依赖「序号差值恒定」这一数学特征:对有序数据按业务逻辑分组后,若某段连续记录的 ROW_NUMBER() OVER (ORDER BY x) 与原始序号(如 id 或时间戳)之差保持不变,则它们属于同一孤岛。
RANK() 和 DENSE_RANK() 在遇到重复值时会跳号或并列,破坏差值稳定性,导致本应连在一起的记录被错误切分。
- 必须用
ROW_NUMBER()—— 它严格生成 1,2,3,4… 不跳不重 - 排序字段必须是能反映真实物理/业务顺序的列,比如
event_time、log_id,不能是可能重复的status或user_id - 如果原始数据本身无序,先用
ORDER BY显式排序,再套窗口函数,否则结果不可靠
LAG() / LEAD() 只适合简单间隙检测,不适合多行孤岛聚合
当只需要判断「当前行和上一行是否中断」(例如检测登录日志中相邻时间差是否 >30 分钟),LAG(event_time) 确实够用;但一旦要合并出完整的孤岛区间(起始 ID、结束 ID、长度),它就力不从心了——你得额外做状态累积、边界标记、再分组聚合,代码膨胀且易错。
-
LAG()返回单个偏移值,无法天然支撑「连续段识别 → 分组 → 聚合」这一完整链路 - 用它实现孤岛,往往要嵌套三层子查询:先算差值标记断点,再用累计和构造分组 ID,最后
GROUP BY,可读性和维护性差 - 真正推荐的写法是:用
ROW_NUMBER() - ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)构造稳定 grouping key
MySQL 8.0+ 中必须注意 NULL 值和排序稳定性
MySQL 对 NULL 的默认排序行为是 NULLS FIRST,而很多业务场景里,NULL 表示“尚未发生”,理应排在末尾。若不显式控制,ROW_NUMBER() 生成的序号会把 NULL 当作最小值处理,导致差值计算错位。
- 所有参与排序的字段都应加上
IS NULL条件兜底,例如:ORDER BY event_time ASC, id ASC改为ORDER BY (event_time IS NULL), event_time ASC, id ASC - 如果排序字段有重复值(比如多个事件在同一秒发生),必须加入唯一辅助字段(如主键
id)保证排序确定性,否则不同执行可能产生不同ROW_NUMBER() - 不要依赖 MySQL 默认的
ORDER BY稳定性,显式写出全部排序依据
真正难的不是写对第一版 SQL,而是当业务规则变复杂(比如孤岛定义从「连续日期」变成「连续日期且 status = 'active'」)、数据质量下降(出现乱序、脏时间戳、缺失值)时,还能快速定位哪一行差值突变了。这时候,把 ROW_NUMBER() 和原始字段并列查出来看几行数据,比翻执行计划快得多。

















