业务单号通常承担两种不同职责
用户希望单号容易辨认和沟通,系统则需要它能够稳定定位一条记录。前缀、日期或分类信息可以改善可读性,但这些结构本身并不能保证并发生成时不重复。
设计时先确定哪些部分服务于展示,哪些部分承担区分记录的职责。将业务含义与生成材料分开,后续调整单号外观时才不会影响底层身份判断。
操作人员需要从单号快速辨认业务类型、日期或渠道,系统则需要稳定地引用唯一记录。如果把这两种需求都压进一条不断变化的格式中,历史记录和外部对接很容易被格式调整牵动。
可以保留稳定的内部标识,同时生成面向业务的展示单号。展示格式需要修改时,内部引用不必改变;但如果外部客户已经使用展示单号查询,它仍需要自己的唯一性与长期兼容约束。
把组成部分写成清楚的格式约定
kind, day, sequence = "SO", "20261005", 42
if not 0 <= sequence <= 999999:
raise ValueError("当日序号超出格式容量")
number = f"{kind}-{day}-{sequence:06d}"
assert number == "SO-20261005-000042"这个片段只负责格式化,不负责分配 sequence。日期格式、时区、序号重置周期与前缀集合,都应提前确定。尤其要说明午夜附近的请求按哪个地区日期归属,否则不同服务可能在同一时刻进入不同序号空间。
固定宽度有利于阅读和排序,但也意味着容量上限。序号达到上限时应停止并报告,不能悄悄增加位数后继续假装仍然遵循原格式,除非协议已经允许这种扩展。
先查最大值再加一,会在并发下碰撞
可以把输入检查、时间或随机材料准备、结果组装分成几个阶段。每个阶段只在前一步成功后继续,并提前判断结果长度是否满足使用场景。
单号所表达的日期需要固定时区含义,避免同一时刻在不同机器上落入不同日期段。发生生成失败时,也不应使用一个固定的备用单号继续业务。
两个请求可能同时读到相同的最大序号,随后都生成下一个值。把这两步放在应用代码中并不形成原子操作,低流量测试通过也不能证明并发时可靠。
import sqlite3
with sqlite3.connect(":memory:") as db:
db.execute("CREATE TABLE documents (number TEXT UNIQUE NOT NULL)")
db.execute("INSERT INTO documents VALUES (?)", ("SO-20261005-000042",))
try:
db.execute("INSERT INTO documents VALUES (?)", ("SO-20261005-000042",))
except sqlite3.IntegrityError:
pass
else:
raise AssertionError("重复单号应被拒绝")序号分配可以由数据库序列、受控计数器或明确的锁范围完成。唯一约束仍然应该保留,作为最终写入边界的保证;它不能替代分配策略,但能够防止错误结果静默落库。
唯一、连续和无空洞,成本并不一样
很多业务说要连续单号,实际只需要单号不重复且大致递增。事务失败、取消单据或预分配都会留下空洞,要完全消除空洞,就需要把编号与业务提交紧密绑定,甚至增加串行协调。
在实施之前应确认空洞是否真的不允许。若只是为了人工核对,记录取消原因或保留作废单据,通常比反复回收旧编号更容易追溯。回收编号还可能让已经发送给外部的引用指向新记录。
| 要求 | 主要代价 |
|---|---|
| 不重复 | 分配机制与唯一约束 |
| 大致递增 | 时间或序列组织 |
| 严格全局递增 | 跨节点协调 |
| 不允许空洞 | 提交时机与取消处理 |
| 可公开查询 | 独立的访问权限规则 |
编号是否能被猜到,不应成为权限保护的主要依据。规则可读的单号通常本来就容易推测,公开查询接口必须检查访问范围,而不是假设别人不知道下一个数字。
用真实生命周期验证单号设计
单号生成与业务记录保存是两个环节。即使生成策略已经降低重复概率,存储层仍应阻止重复记录,并给调用方明确的处理结果。
测试应包含密集生成、跨日边界和多进程场景。对历史数据而言,格式变更最好通过显式版本或兼容规则管理,避免新规则把旧单号解释成另一种业务含义。
测试不仅要生成几个正确字符串,还要覆盖跨日、重试、取消、并发冲突和序号耗尽。若调用方因网络超时重试,同一业务操作是否应复用原单号,需要结合幂等请求设计决定。
展示格式升级时,应保留解析旧单号的能力,或让查询只把单号当作不透明字符串,不依赖拆分其中的日期来寻找记录。后一种路线通常更容易保持兼容。
生成时间、业务发生时间与入库时间可能不同。单号中的日期究竟代表哪一个,应当在文档中固定下来,否则人工依据单号进行统计时就可能得到与系统报表不一致的结论。