NEXT VALUE FOR 配合 SEQUENCE 是最稳妥的选择,因其原生支持高并发、不跳号、可跨表复用,且 SQL Server 2012+ 全面可用;而 MAX(id)+1 等方式在并发下必然重复,事务回滚还会导致永久跳号。

直接用 NEXT VALUE FOR 配合 SEQUENCE 是最稳妥的选择——它原生支持高并发、不跳号、可跨表复用,且 SQL Server 2012+ 全面可用。
为什么不能用 MAX(id)+1 或 SELECT TOP 1 ... ORDER BY id DESC
这类写法在并发场景下必然重复。两个事务同时查到当前最大值为 100,各自加 1 后都插入 101,编号就撞了。即使加了 WITH (UPDLOCK, ROWLOCK),也挡不住高并发下的间隙锁争抢和死锁风险。更麻烦的是:一旦事务回滚,编号就永久丢失(跳号),业务上常被质疑“为什么中间缺号”。
CREATE SEQUENCE 的关键参数怎么设才不出错
必须显式指定 START WITH、INCREMENT BY、MINVALUE/MAXVALUE 和 CACHE,否则默认行为容易踩坑:
-
START WITH要比现有最大编号大 1,比如当前最大订单号是 9999,就得写START WITH 10000 -
INCREMENT BY别设成负数,除非你真要倒序生成;日常用INCREMENT BY 1即可 -
CACHE 100能显著提升性能,但实例意外重启会丢失缓存中未用完的号(最多丢 100 个),若业务零容忍跳号,改用NOCACHE - 务必加
NO CYCLE,避免用尽后从头循环导致编号重复(比如从 9999 又回到 0001)
示例:
CREATE SEQUENCE dbo.OrderNoSeq
AS bigint
START WITH 10000
INCREMENT BY 1
MINVALUE 1
MAXVALUE 999999999
NO CYCLE
CACHE 50;
如何拼接前缀/日期并保证原子性
编号逻辑不能拆成“先取序列值 → 再拼字符串 → 再插入”,中间任何环节失败都会导致序列号浪费或状态不一致。正确做法是在 INSERT 语句里直接调用:
- 纯数字编号:
INSERT INTO Orders(OrderNo, ...) VALUES(NEXT VALUE FOR dbo.OrderNoSeq, ...) - 带日期前缀(如
ORD202607210001):INSERT INTO Orders(OrderNo, ...) VALUES('ORD' + FORMAT(GETDATE(), 'yyyyMMdd') + RIGHT('0000' + CAST(NEXT VALUE FOR dbo.OrderNoSeq AS VARCHAR(10)), 4), ...) - 如果拼接逻辑复杂,封装成标量函数也行,但函数内不能含数据修改语句,否则会报错
注意:NEXT VALUE FOR 在同一语句中多次调用返回相同值,所以别写成 'ORD'+...+NEXT VALUE FOR... 再 + NEXT VALUE FOR...,否则会多消耗一个号。
存储过程里调用序列时最容易忽略的点
很多人把序列号生成逻辑包进存储过程,却忘了事务边界和错误处理:
- 如果存储过程里先执行
NEXT VALUE FOR,再做其他校验失败回滚,那个序列号已经消耗掉了,无法退还 -
NEXT VALUE FOR本身不参与事务回滚,这是设计使然,不是 bug - 若需“预占号+条件确认”,得用单独表记录待分配号(带状态字段),由应用控制生命周期,而不是依赖序列对象
- 跨库调用时,确保序列对象在目标数据库中存在,
NEXT VALUE FOR不支持三段式名称(如db.schema.seq),只能两段(schema.seq)
真正难的不是生成编号,而是让编号规则、消耗节奏、异常恢复这三者对齐。序列只是工具,业务语义还得靠外围逻辑兜底。

















