连载中 1/16

订单号撞车之后:为什么需要分布式 ID

2026-06-05 · 6141 阅读 · 0 评论 · 0 赞

一场跨用户的退款事故

四大战场打完(Redis 缓存 16 篇、MySQL 实战 22 篇、消息队列 20 篇、Elasticsearch 20 篇),503 咖啡馆的商城业务也起飞了:单日订单冲破 50 万,单库单表先扛不住。老王照着 MySQL 系列里分库分表的路线图,把订单库按用户维度拆成了 16 个库、每库 16 张表,一共 256 张订单表,顺理成章。

上线第三天,客服炸群:用户 A 申请退款,退的是用户 B 的订单;隔壁用户 B 发工单说订单凭空消失。老王连夜排查,最后盯住两个字段——两张不同表里的订单,ID 一模一样,都是 1001。

根因朴素得扎心:拆表之前,订单 ID 靠单库 auto_increment 发号,天然全局唯一;拆成 256 张表之后,每张表各自从 1 开始发号,「全局唯一」四个字当场作废。A 的 1001 落在表一,B 的 1001 落在表二,按 ID 检索订单的退款请求,指错了人。

auto_increment 在分布式下的三宗罪

第一宗罪:多库必撞车。auto_increment 是表级语义,只保证一张表内唯一。分库分表后,256 个发号器各发各的,撞车是必然不是偶然,除非在它上面再叠一层全局调度。

第二宗罪:扩容是灾难现场。就算拆表时用步长错开了起点(下面马上讲),扩容要重排起点与步长,老号段发完即断档;想避免重排,就得提前预留一大把空库空表,白白占着部署资源。

第三宗罪:ID 出卖业务。连续自增的订单号等于把销量贴在脸上:竞对每天下一单,盯你号涨多快,日单量、增速全被拿捏;订单号还能被遍历刷接口。这也是本系列后面专门留一篇讲 ID 安全的原因。

步长方案:救急不救命

先看只靠数据库能凑合的方案:步长发号。16 张表,起始值分别 1 到 16,步长统一 16:

表 0 发号:1, 17, 33, 49 ...
表 1 发号:2, 18, 34, 50 ...
表 15 发号:16, 32, 48, 64 ...
起点错开、步长拉齐,号段永不交叉

这招能立刻止住撞车,老王上线第一周就靠它稳住场面。但硬伤摆在明面上:扩到 32 张表时,步长要从 16 改成 32,所有表的发号起点重排,得停写对齐;多条业务线共用一套规则时步长互相牵连;号段依赖每张表自身的 auto_increment,发号压力还是压在订单库上。这套方案适合「确定不再扩容的小规模」,规模一上来,它是定时炸弹。

业务到底要什么样的 ID

事故复盘会上,老王把订单系统对 ID 的需求写成一页纸,五条:

要求为什么反面教材
全局唯一底线要求,撞号直接资损拆表后两库同号,退款退错人
趋势递增主键顺序写不翻页,索引不折腾随机主键让 B+ 树页分裂频发(第 11 篇细讲)
不暴露量级防竞对侦查与恶意遍历连续订单号泄露日单量
高可用高并发发号是全站下单的前置依赖发号器一挂,全网不能下单
长度克制64 位整型省索引省内存超长字符串主键撑大二级索引

五条摁下去才发现,没有一款现成方案是五指齐全的——分布式 ID 的本质是权衡:要并发就难绝对连续,要无依赖就难管住时钟,要信息安全就得牺牲一点可读性。

四条候选路线,混个脸熟

路线核心原理一句话点评
UUID本机随机生成 128 位零依赖,但无序又肥,索引吃亏
号段模式DB 批量取一段号,内存里慢慢发趋势递增,DB 压力小,增加一个发号服务
雪花算法时间戳加机器号加序列拼 64 位本地发号吞吐高,怕时钟回拨
Redis 发号INCR 原子自增简单直接,可用性绑在 Redis 上

本系列的行进路线:先拆 UUID,看它到底能不能当订单主键;再讲号段模式这个「数据库正解」,以及它的双 buffer 进阶;主战场是雪花算法——结构拆解、时钟回拨这个死穴的原理与三种治理、workerID 分配之争;Redis 发号作为轻量备选;然后是开源方案选型、趋势递增的索引意义、ID 安全、分片基因与高可用,陷阱集和收官地图压轴。

小结

一句话:单库时代 auto_increment 是白给的福利,分库分表后它是事故的源头。分布式 ID 是系统变大后的第一个共识问题:先保证唯一,再谈好用、好看、扛造。下一篇拆 UUID——这把最顺手的瑞士军刀,为什么大厂订单表基本不用它当主键。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式ID#分库分表#订单号#自增ID#唯一ID

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞