lag()和row_number()直接比对当前值与上一行值是最直观断号检测方式:lag()获取前一行seq_id,row_number()生成理想序号,二者相减暴露缺口;需先去重排序再开窗,否则结果不可靠。

用 LAG() 和 ROW_NUMBER() 找出断号位置
直接比对当前值和上一行的值,是最直观的断号检测方式。关键不是“查有没有缺失”,而是“在哪断的”。LAG() 拿上一行的序列号,ROW_NUMBER() 生成理想连续序号,两者一减就能暴露缺口。
- 假设表
t_log有字段seq_id(非空整数),且理论上应从 1 开始连续递增 - 先按
seq_id排序,再用LAG(seq_id) OVER (ORDER BY seq_id)获取前一个值 - 当
seq_id - LAG(seq_id) OVER (...) > 1,说明中间至少缺一个号;等于 0 表示重复,也得警惕 - 如果原始数据本身无序或含重复,必须先去重、排序再开窗,否则
LAG()结果不可靠
用 GENERATE_SERIES()(PostgreSQL)或 numbers 表补全后反查缺失
有些场景需要列出所有缺失值(不止断点位置),这时靠差集更稳妥。PostgreSQL 有内置 GENERATE_SERIES(),MySQL 8.0+ 可用 CTE 递归生成,SQL Server 用 master..spt_values 或 VALUES 构造小范围序列。
- 先查出
MIN(seq_id)和MAX(seq_id),作为补全范围上下界 - 生成完整序列后
LEFT JOIN原表,WHERE t_log.seq_id IS NULL即得缺失项 - 注意:范围过大时
GENERATE_SERIES(1, 1000000)会拖慢查询,建议限制在合理区间(如最近 10000 条内) - 若缺失集中在某段(比如只关心 5000–5100),务必加
WHERE过滤生成范围,别硬扫全量
LEAD() 也能用,但容易漏掉末尾断点
LEAD() 看下一行,适合检测“当前号之后是否跳变”,但无法发现最大值之后的缺失(比如现有最大是 99,实际该到 105,这 5 个就查不到)。除非你额外补一个“预期最大值”做边界判断,否则不推荐单用 LEAD()。
- 典型误用:
LEAD(seq_id) - seq_id > 1→ 只能抓到 99→102 这种中间断,抓不到 105 之后的空档 - 若业务明确知道理论终点(如每日固定生成 100 条),可加条件
seq_id 来捕获末尾缺失 - 多数情况下,
LAG()+ 起始校验(检查是否从 1 开始)+ 终止校验(对比理论总数)三者组合才完整
性能陷阱:窗口函数在无索引列上排序极慢
如果 seq_id 没建索引,ORDER BY seq_id 会让 LAG() / ROW_NUMBER() 全表排序,100 万行可能卡数秒。这不是语法问题,是执行计划问题。
- 确认执行计划里
WindowAgg节点是否走了Index Scan,而不是Sort+Seq Scan - 临时方案:加
WHERE seq_id BETWEEN ? AND ?缩小范围,再开窗,比全量快得多 - 长期方案:给
seq_id加普通 B-tree 索引(唯一性另说,排序才是关键) - 特别提醒:某些旧版 MySQL 不支持窗口函数,报错
ERROR 1064: This version of MySQL doesn't yet support 'WINDOW',得换 8.0+
实际跑起来,最常卡住的不是逻辑,是没索引的排序和没设边界的 GENERATE_SERIES()。先看执行计划,再动手写。










