next value for 配合 sequence 是唯一兼顾连续性、并发安全与可维护性的方案;max(id)+1 等方式在并发下必然重复且事务回滚后永久跳号,因其违反原子性,无法避免读-改-写竞争。

NEXT VALUE FOR 配合 SEQUENCE 对象,是唯一能兼顾连续性、并发安全和可维护性的方案;所有基于 MAX(id)+1 或 SELECT TOP 1 ... ORDER BY 的写法,在并发场景下必然重复,且事务回滚后永久跳号。
为什么 MAX(id)+1 在存储过程中一定不可靠
这不是“偶尔出错”,而是数据库底层并发模型决定的必然失败:
- 两个会话同时执行
SELECT MAX(id) FROM Orders,都拿到 9999 - 各自加 1 后插入
10000,主键冲突或业务编号重复 - 即使加了
WITH (UPDLOCK, ROWLOCK),也无法阻止间隙锁争抢,高并发下极易死锁 - 若其中一个事务回滚,
10000就永远丢失——业务方追问“为什么缺号”,你无法解释
CREATE SEQUENCE 必须显式指定的参数
漏设任意一项,都可能在上线后突然失效或跳号:
-
START WITH:必须比当前最大编号大 1,比如现有最大订单号是12345,就得写START WITH 12346 -
INCREMENT BY 1:别设负数,除非真要倒序;日常场景只用1 -
CACHE 50:提升性能,但 SQL Server 意外宕机时最多丢 50 个号;零容忍跳号就改用NOCACHE -
NO CYCLE:必须加!否则用尽后从头循环(如9999 → 0001),导致编号重复 -
MINVALUE/MAXVALUE:不设则默认为bigint极值,但业务通常有长度约束,建议显式限定(如MAXVALUE 999999999)
示例:
CREATE SEQUENCE dbo.OrderNoSeq AS bigint START WITH 10000 INCREMENT BY 1 MINVALUE 1 MAXVALUE 999999999 NO CYCLE CACHE 50;
NEXT VALUE FOR 拼接前缀时的原子性陷阱
编号生成和字符串拼接必须在单条 INSERT 语句内完成,拆成两步就会浪费号或状态不一致:
- 错误写法:
DECLARE @seq BIGINT = NEXT VALUE FOR dbo.OrderNoSeq; INSERT INTO Orders(OrderNo) VALUES('ORD' + FORMAT(GETDATE(), 'yyyyMMdd') + RIGHT('0000'+CAST(@seq AS VARCHAR), 4))—— 若INSERT失败,@seq已消耗,不可回退 - 正确写法:直接在
VALUES中调用,让 SQL Server 一次性完成取号+拼接+插入INSERT INTO Orders(OrderNo) VALUES('ORD' + CONVERT(char(8), GETDATE(), 112) + RIGHT('0000' + CAST(NEXT VALUE FOR dbo.OrderNoSeq AS VARCHAR(10)), 4)); - 注意:
NEXT VALUE FOR在同一语句中多次出现返回相同值,所以别写成'ORD' + NEXT VALUE FOR ... + NEXT VALUE FOR ...,会多占一个号
按天重置流水号(如 ORD202608040001)怎么避免跨天竞争
凌晨多个请求同时判断“今天没记录”,各自初始化为 0001,结果一堆 ORD202608040001:
- 解决方案:把日期也作为号段表的联合键,例如
Key = 'ORDER'+DatePart = '20260804' - 先尝试插入新日期行:
INSERT INTO SeqConfig(Key, DatePart, CurrentValue) SELECT 'ORDER', '20260804', 0 WHERE NOT EXISTS (SELECT 1 FROM SeqConfig WHERE Key = 'ORDER' AND DatePart = '20260804') - 再执行带条件的
UPDATE ... OUTPUT,确保只更新当天记录 - 日期格式统一用
CONVERT(char(8), GETDATE(), 112),避免区域设置影响(FORMAT()函数线程不安全,且性能差,别用)
真正难的不是写代码,而是想清楚“连续”到底指什么:是数据库物理连续?还是业务意义上不跳号、不重复、可追溯?前者靠 SEQUENCE + 合理参数,后者还得靠号段表设计和严格控制拼接时机。











