能,但必须将“格式拼接”与“号段分配”分离处理:前者为字符串操作,后者才是并发安全核心;高并发下禁用select max()+update,应采用update+output原子分配号段,并以专用表(key、currentvalue等字段)配合where条件与output子句实现可靠取号。

能,但必须把“格式拼接”和“号段分配”拆开处理——格式是字符串操作,号段才是并发安全的核心。直接在存储过程中用 SELECT MAX() + UPDATE 拼流水号,在高并发下大概率产生重复或死锁。
用 UPDATE + OUTPUT 实现原子号段分配
这是最稳妥的落地方式:靠单条 UPDATE 语句完成“读取当前值→加1→写回→返回新值”四步,全程原子,不依赖事务隔离级别。
- 必须用一张专用号段表(如
SeqConfig),字段至少含Key(业务类型标识)、CurrentValue、LastUpdate -
UPDATE必须带WHERE Key = @Key条件,且不能漏掉OUTPUT INSERTED.CurrentValue - 别用
SELECT ... THEN UPDATE两步法——中间窗口期会被其他会话抢占 - 示例关键片段:
UPDATE SeqConfig SET CurrentValue = CurrentValue + 1 OUTPUT INSERTED.CurrentValue WHERE Key = @BizType
日期+流水号组合时,避免跨天竞争
按天重置流水号(如 ORD202606150001)看似简单,但凌晨多个请求同时发现“当天无记录”,就会各自插入初始值,导致冲突。
- 解决方案:把日期也作为号段表的联合主键(
Key + DatePart),例如Key='ORDER', DatePart='20260615' - 插入前先
INSERT ... SELECT尝试写入新日期行,用IF @@ROWCOUNT = 0判断是否已存在,再执行UPDATE - 别用
GETDATE()直接拼WHERE条件——不同会话执行时间毫秒级差异可能导致查不到刚插入的行 - 日期格式统一用
CONVERT(char(8), GETDATE(), 112)(即yyyymmdd),不依赖语言/区域设置
补零、前缀、长度控制别用 FORMAT()
FORMAT() 在 SQL Server 2012+ 可用,但它性能差、线程不安全,且在某些版本中会因文化设置导致意外空格或符号。
- 固定位数补零一律用
RIGHT('000000' + CAST(@num AS VARCHAR(10)), 6) - 前缀拼接直接用
+,不要在存储过程中做拼音缩写逻辑——那属于应用层职责,数据库只负责“给号” - 如果业务要求流水号总长固定(如 14 位),建议在应用层校验,数据库层不做截断——截断可能掩盖真实并发问题
- 避免在拼接逻辑里调用函数如
REPLACE()或嵌套CONVERT(),每多一层都增加执行计划不稳定风险
SEQUENCE 对象只适合弱一致性场景
它快、轻量,但 NEXT VALUE FOR 不受事务控制——事务回滚后号已跳过,且无法按业务维度分组重置。
- 仅适用于日志 ID、追踪编号等“只要唯一、不怕跳号、不需回滚同步”的场景
- 若用它,务必配
CACHE 1(禁用缓存),否则服务器重启会丢失缓存号段,造成更大跳跃 - 别把它和日期逻辑混写进同一存储过程——
SEQUENCE生成的是纯数字,日期部分必须单独计算并拼接 - 错误示范:
SELECT @No = @Prefix + CONVERT(VARCHAR, NEXT VALUE FOR seq) + ...—— 这里NEXT VALUE FOR调用和后续拼接不在同一原子上下文
真正难的不是拼字符串,而是让“取号”这个动作在分布式、多会话、跨天、可回滚的环境下不翻车。所有花哨的格式规则,都得建立在号段分配绝对可靠的基础上;一旦底层号源乱了,上层再怎么补零、加前缀都没意义。











