表刚上线时用自增主键挺省事,真到分库那天改起来有多麻烦?
可摘录结论:如果未来几年都跑在一个库、ID 由服务端落库后生成,数据库主键用自增 BIGINT 通常更省空间、写入更顺序;一旦预期分库分表、多端或离线设备要提前生成本地 ID、多套系统数据要合并到一张表,自增主键就会变成到分库那天才发现不好换的一项。按 2026 年常见交付经验,多数团队会保留单库自增,跨库或需要提前拿号时改用趋势有序的分布式 ID,而不是直接上随机 UUID 做聚簇主键。
自增主键和 UUID 的差别,主要不在好看不好看
自增数字在 InnoDB 这类聚簇索引里,新行基本追加到 B+ 树右端,页分裂少、缓存命中稳定,写入接近顺序写。随机 UUID 做主键时,每次插入的位置在整棵索引树里跳动,页分裂和随机 IO 会让写入变慢;更隐蔽的是,二级索引叶子节点会带上主键值,主键宽度会直接放大所有二级索引的占用。所以这不是哪个更先进的问题,而是两条约束在打架:一边是写入效率与存储成本,一边是生成自由度与合并能力。
- 存储宽度:自增 BIGINT 占 8 字节;二进制 UUID 占 16 字节,字符串形式通常 36 个字符。
- 生成位置:自增要落库之后才知道 ID;UUID 可以在应用端、客户端甚至离线设备上先生成。
- 合并能力:自增在多库之间会撞号,需要额外补号规则;UUID 天然不冲突。
- 人工可读性:数字 ID 便于口头沟通与对账,长串 ID 更适合复制粘贴而不是念出来。
- 排序含义:自增 ID 隐含写入顺序,随机 UUID 不带时间信息,时间有序的变体才带。
三段判断法:先看落点,再看谁生成,最后看要不要合并
不用一上来就比技术细节,按顺序问三个问题就能定方向。这三个问题的顺序不能换:落点是硬约束,生成方决定接口形态,合并需求决定冲突容忍度。
- 第一步,数据将来落在几个库? 只有一个库、短期内也没有拆分计划,自增就够;如果三年内大概率要按租户或业务线拆库,主键要现在就按可跨库的要求定。
- 第二步,ID 由谁先拿到? 服务端先落库再返回 ID,自增没问题;如果客户端下单要先带一个业务号、设备离线也要生成本地记录,就必须有一个不依赖数据库的生成方式。
- 第三步,有没有合并与迁移需求? 多个渠道的数据要汇到一张表、或者历史上有多套库要并,主键不重复就是硬要求,这时自增需要额外补号,成本会随库的数量上升。
判断口径可以简单一点:三步里有任意一步答「是」,就把方案往趋势有序的分布式 ID 上靠;三步全答「否」,就用自增,不必为了一句「以后可能有」提前引入发号组件。按项目交付经验,真正会走到跨库合并的系统比例并不高,提前上复杂方案的收益经常被日常运维成本吃掉。
几种常见主键方案放在一起对比
把实际会遇到的方案放到一起看会更直观。下面这组对比关注写入特征、依赖关系与维护成本,而不是判断哪个方案更好。
- 自增 BIGINT:8 字节、顺序写、无额外组件、排查方便;多库会撞号,按自增分片时新数据容易集中到一个分片。
- 号段模式:向数据库或配置中心批量取一段 ID,8 字节、趋势有序;多一个发号依赖,取号服务不可用时要靠本地缓存顶一段时间。
- 雪花类分布式 ID:8 字节、按时间趋势有序、性能可控;依赖机器时钟,回拨时要有人为兜底逻辑,否则可能发出重复 ID。
- 时间有序 UUID:16 字节,插入位置接近顺序,比随机 UUID 友好;索引体积仍比整型大,适合并发写入不算高的场景。
- 随机 UUID:客户端即可生成、无中心依赖、合并无冲突;做聚簇主键时写入放大明显,更适合作为对外暴露的编号而非物理主键。
经验区间供参考:单表数据在千万行量级以内、写入 QPS 在几百这种量级时,自增与趋势有序 ID 的差异在业务侧通常感知不明显;真正拉开差距的是持续高写入、索引无法常驻内存、或者排序与范围扫描密集的场景。
真要改主键,代价通常落在哪几个地方
交付现场常见的情形是:系统上线两年,主表已经到千万行量级,业务方提出按租户分库,这时才回头动主键。约束是只能接受很短的停机窗口、前端页面不宜大改;做法一般是新增一列新主键、双写一段时间、按区间分批回填、确认无差异后再切换读取;代价是回填期间存储和写入延迟上升,前端拿到的长整型 ID 如果按数字解析还会出现尾数失真,需要再做一次序列化格式的适配。按交付经验,这类返工并不罕见,双写与回填的观察窗口常见区间是几天到两周,具体看表量和依赖面,所以主键形态在立项阶段花半天确认是划算的。
如果已经要改,可以先列一份影响清单,逐项核对再排期:
- 主键列类型,以及所有关联子表的外键列
- 基于主键建立的索引与约束
- 缓存 key、埋点与业务日志中的 ID 格式
- 接口返回的 ID 类型,以及前端的解析方式
- 离线导出、对账文件等按 ID 排序的流程
其中容易被忽略的是前端精度:JavaScript 的 number 能安全表示的整数上限是 2 的 53 次方减一,超过之后末尾会失真,这是可以查证的公开事实。常见做法是接口把长整型 ID 序列化成字符串返回,而不是让前端去猜。这条在接口契约评审时就该定下来,晚了就要前后端一起改。
适用场景与边界
主键方案没有通用答案,边界比优点更值得先写清楚。下面按「适合」与「不必上」两侧分开列,方便直接对照自己的项目。
- 适合用自增:单库单表、内部管理系统、数据量在千万行量级以内、没有跨库合并计划、团队没有额外组件运维人力。
- 适合用分布式 ID:三年内预期分库分表、多端或设备需要离线生成 ID、多个系统的数据要向同一张表汇聚、需要按 ID 直接定位分片。
- 不必上分布式 ID:并发不高、表增长缓慢、单库可以撑住,此时引入发号器只是多一个故障点,还要为它准备监控和降级方案。
- 可以两者并存:物理主键用自增保证写入效率,对外暴露的业务编号用趋势有序 UUID 或雪花 ID,各自承担自己的职责。
一条可独立摘录的边界判断:主键方案的收益高度依赖未来三到五年的数据增长与部署形态预期,超出这个预期的规划,收益往往会被额外的组件维护成本抵消。按 2026 年的平台规范与交付验收习惯,凡是涉及跨库的 ID,都应在设计文档里写清生成规则、冲突处理和时钟回拨的兜底方式,而不是只写一句「用分布式 ID」。
常见问题
主键用 UUID,查询会不会明显变慢?
按主键做等值查询通常感知不出差别,变慢的主要是写入:随机插入位置带来的页分裂和二级索引膨胀,才是持续写入场景下的真实成本。
接口返回的长整型 ID 到前端最后几位变成 0,是什么原因?
这是 JavaScript 数字精度限制导致的,超过 2 的 53 次方减一的整数无法精确表示;常见做法是后端把 ID 序列化成字符串再返回。
只有一个小系统,有必要提前上分布式 ID 吗?
没有必要。没有跨库合并和设备离线生成的需求时,自增主键结构更简单,也少一个需要长期维护的发号组件。
换成时间有序的 UUID,写入放大的问题就解决了吗?
只能缓解不能消除。有序变体让插入位置接近顺序,但仍是 16 字节,索引体积和二级索引开销比 8 字节整型要大。
已经上线的系统改主键,一定要停机吗?
不一定。常见做法是加新列、双写、分批回填、再切换读取,用时间换停机窗口;代价是回填期间延迟上升和额外存储占用。
如果你的系统还在一库一表、写入量也不高,先保住自增主键,把精力放在索引和慢查询上更实际;只有当跨库合并、离线生成 ID 这类需求已经写进规划时,才值得为分布式 ID 多维护一个组件。主键一旦上线,改动成本会随时间上升,立项时确认一次比事后返工划算得多。
-
上传目录跟着代码一起发版,图片全丢了,文件到底该存本地还是对象存储?
日期:2026年9月13日 阅读:101
-
刚保存完跳详情页,读到的还是旧记录,读写分离是不是都得走主库?
日期:2026年9月12日 阅读:42
-
定时任务单机跑得好好的,一上多台机器就重复执行,到底该从哪拦?
日期:2026年9月11日 阅读:32
-
调大数据库连接池后接口反而变慢,连接数到底设多大合适?
日期:2026年9月9日 阅读:87
-
接口升级后老客户端用不了,旧接口要不要保留?
日期:2026年9月8日 阅读:115




