面试官:线上MySQL的自增id用尽怎么办?

面试官:线上MySQL的自增id用尽怎么办?

作为一个常常跟数据库打交道的 Java 开发,我最近看到一个挺有意思也挺吓人的问题:MySQL 的自增 ID 用尽了,会发生什么?

这个问题不难理解,毕竟自增 ID 是我们天天都在用的东西,不管是订单号、用户ID、文章ID,只要你用到数据库,多半绕不开它。但大多数人只在乎“它怎么用”,压根没想过“它能用多久”、“用完了咋办”。

结果呢?用完了还真不是小事,搞不好还会造成数据覆盖,甚至是脏读这种让 DBA 头皮发麻的问题。

今天我就带你从一个 Java 工程师的视角,通俗又接地气地聊聊这个话题,技术味别太浓,但必定让你听清楚。

表自增 ID,用完就崩

我们一般在建表时都喜爱这样写:

CREATE TABLE t (
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50)
);

这里 INT UNSIGNED 占 4 个字节,最大值是 2^32 – 1,也就是 4294967295。

那这个数字大吗?不好说,取决于你这张表是不是“大胃王”。

如果是高频写入的表,列如日志表、电商订单表,每天几百万条数据不是梦。理论上说,一张表如果每天插入 100 万条记录,大致 4 年就能插满这个自增 ID。

我试过在 MySQL 里建张表,手动把 AUTO_INCREMENT 设置到最大,然后再插入一条记录,结果当然是成功的,ID 就是 4294967295。

再插一条?MySQL 报错了:

ERROR 1062 (23000): Duplicate entry '4294967295' for key 'PRIMARY'

什么意思?自增 ID 用完了,它还能继续生成下一个 ID 吗?不能。MySQL 会傻傻地继续拿那个已经用过的最大值,于是就撞主键了。

所以,用完了就等于炸了,这个时候要么你自己换字段类型(列如升成 BIGINT),要么就得挪窝了,换张新表重新开始。

当然,最好的办法是你一开始就用 BIGINT UNSIGNED,这个最大值是 2^64 – 1,大致是 1.8 x 10^19,一般人一辈子都用不完。

但谁知道公司项目能火到啥程度呢?所以,一开始就用大号锅,总比锅炸了再换来得从容。

没主键?InnoDB 给你造一个,但有坑

许多时候,为了图省事,有人建表时直接就没有主键。你以为没关系,InnoDB 心里可着急了。

由于 InnoDB 要为每一行数据都分配一个唯一标识符,要不然它怎么定位数据?于是它就偷偷地在后台搞了个隐藏字段,叫 row_id,6 个字节大小。

注意,虽然 row_id 的生成是基于一个 bigint unsigned(也就是 8 字节),但 InnoDB 实际写进数据页里的是最后 6 个字节

换句话说,真正能用的 ID 范围是 0 到 2^48 – 1,最多大致 281 万亿条数据。听起来比前面 int 的 42 亿大得多,对吧?

但问题来了:用完了呢?

对不起,它会从 0 再开始。你没听错,它会循环使用 ID

那么,假设你不小心已经写了 2^48 条记录,再插一条,拿到的是 row_id=0,如果表里已经有 row_id=0 的数据,那怎么办?覆盖

这不是开玩笑,我真在测试环境复现过:用 GDB 把 dict_sys->row_id 改成 2^48,下一条 insert 会直接把 row_id=0 的那行数据覆盖掉。再下一条,row_id=1,继续覆盖。

天哪,这比插入失败更吓人,直接数据丢了还不报错,你说你改了什么,领导都不知道咋查。

所以,别偷懒,该建主键就建主键,别指望 InnoDB 的“好心”。

自增事务 ID,也会绕圈子

咱写 Java 项目的时候,常常用到事务对吧?MySQL 为了记录事务,维护了一个叫 trx_id 的变量,每当开启一个新事务,它就 +1 分配一个 ID。

这个 trx_id 也是 8 字节,但在 MySQL 的实现里,有个“低水位”的概念。系统会记录当前活跃事务的最小 trx_id,用来判断某条数据是否对你可见。

如果一个事务更新了某条记录,那么这条记录上就会打上这个事务的 trx_id 标签。

当另一个事务要读这个记录时,它就会用自己的视图去判断这条记录“是不是我应该看到的”。

这就是大家说的数据可见性判断机制,核心就是“我能看到比我事务早提交的,但看不到比我晚提交的”。

问题又来了:trx_id 是有限的,如果用到 2^48 – 1,然后下一个是 0,系统还认为 0 是个比你早提交的事务,你就可能读到了一个还没提交或者刚提交的数据。

这就叫——脏读

我真在测试里干过这事,把 max_trx_id 改到最大,然后开两个 session,一个更新数据,一个读取,结果读到了根本不该看到的数据。

是不是开始觉得后背有点凉了?

这个 bug 是 MySQL 早年版本留下的隐患,虽然得跑个十几年才可能遇到一次,但它的确 是“理论上会发生”。

而且,MySQL 重启后这个 max_trx_id 是会被持久化的,不会清零。这意味着重启也救不了你。

所以,在一些对数据一致性极度敏感的系统,列如银行交易、核心账本、用户资产系统,你就不能完全信任 MySQL 的这一套机制。

还有 Xid 和 Thread ID,那都是内存变量

说到事务,再扯点边的。MySQL 的 redo log 和 binlog 都有个叫 Xid 的东西,表明事务编号。

Xid 来自于一个内存变量 global_query_id,每执行一条语句就加 1。如果是事务的第一条语句,它就会拿这个 ID 当作事务号。

那这个变量多大呢?8 字节,最大 2^64 – 1。看上去比前面那些都安全多了。

更重大的是,这个变量在 MySQL 重启之后会清零。所以你即便用到最大也不怕,重启一下就完事了。

唯一需要注意的是,同一个 binlog 文件里,Xid 不能重复。虽然理论上有可能,但你真要执行 2^64 条语句,也得跑几百万年,所以这个咱可以放心。

还有个小玩意叫 thread_id,也就是你在 show processlist 看到的连接线程 ID,这个是 4 字节,最大 2^32 – 1,用完也会归 0。

但 MySQL 给每个线程分配 ID 的时候有个机制,会查一个唯一数组,确保你不会看到两个一样的线程 ID 同时存在。这个咱也可以放心。

别觉得数字用不完,计算机哪有无限的

看完是不是感觉有点后怕?实则说到底,所有这些问题本质上是一样的:数字是有限的,别拿它当无限的用

做为 Java 工程师,我们在设计表结构、生成主键 ID、实现分布式系统时,都要意识到这个问题。

列如:

  • 建表就用 bigint unsigned,别用 int。
  • ID 必定要思考长远,别小看你的业务增长速度。
  • 主键自己定义,别让 InnoDB 给你“乱造”。
  • 有些系统需要分布式 ID,别指望数据库自增。
  • 数据一致性不能全靠数据库,逻辑上也要兜底。

我见过一个系统,订单 ID 用的是 int,结果春节期间订单量爆了,后端一直报主键冲突,最后直接挂了,半天恢复不过来。

所以说,系统的天花板,往往就是你起步时埋下的坑

小坑能踩,大坑就得重新挖了。

别以为“用完 ID”是遥远的未来,技术人要有点危机感,别被现实教育了才来补锅。

这事,不光是 DBA 的责任,更是开发的基本功。别做那个写完代码拍拍屁股走人的人,做那个想在前头、稳在后面的靠谱工程师。

© 版权声明

相关文章

19 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    檀健次我想要两颗西柚_ 投稿者

    8000工资考这个?

    无记录
  • 头像
    浪里各浪 读者

    用完,想啥呢

    无记录
  • 头像
    文艺中得搬运工 读者

    bigint

    无记录
  • 头像
    荼蘼死了 读者

    能用完?

    无记录
  • 头像
    心很软 读者

    💗感谢分享

    无记录
  • 头像
    越来越好 读者

    收藏了,感谢分享

    无记录
  • 头像
    小北极 读者

    面试造火箭,实际拉大锯

    无记录
  • 头像
    丿 读者

    我看我们系统都是雪花

    无记录
  • 头像
    展览会招展 读者

    换家公司,继续自增

    无记录
  • 头像
    小島空白詩 投稿者

    用尽了 你就该换工作了

    无记录
  • 头像
    徐斌 读者

    bigint能用尽,基本不存在。这个数大到全世界所有的硬盘加起来都不够存你的数据。

    无记录
  • 头像
    柠九 读者

    用完跑路吧,反正也干不了太久

    无记录
  • 头像
    _是晓不是xiao 投稿者

    4年嘛,问题不大,中国在一家公司干四年的概率不大,早都被开了,既然都被开了或者自己走了,那还担心个屁,像这样还能为后来者提供工作量和贡献的,简直是大功德啊。

    无记录
  • 头像
    飒飒9293 投稿者

    你不说还真不知道有用完这个说法,不过能用完的都是独角兽级别的公司了,腾讯阿里各大银行这种。

    无记录
  • 头像
    静悄悄 读者

    哈哈,用尽?你想多了吧,雪花算法都用不尽,自增怎么用尽

    无记录
  • 头像
    一等星 读者

    用雪花

    无记录
  • 头像
    不流泪女人 投稿者

    你就直接跟他说,你用不完

    无记录
  • 头像
    TiAmo-mmm 投稿者

    ID用尽之前就会达到性能瓶颈了,,就会迫不得已进行处理,,比如归档旧数据等等。。不过文章挺好,还是需要明白这些

    无记录
  • 头像
    羙照 投稿者

    我会问他你们公司做啥业务的,数据量能大到bigint的自增id都能用完。

    无记录