0

0

在分布式环境下,如何保证MySQL主键的唯一性?

紅蓮之龍

紅蓮之龍

发布时间:2025-09-10 13:00:02

|

232人浏览过

|

来源于php中文网

原创

答案:分布式系统中MySQL主键唯一性需脱离AUTO_INCREMENT,采用全局ID生成方案。核心方法包括Snowflake算法(时间戳+机器ID+序列号,保证唯一且递增)、UUID(128位随机唯一,去中心化但无序)、数据库号段模式(如Leaf,预取号段提升性能)及Redis自增(高性能但依赖中心化)。每种方案各有优劣,适用于不同场景。

在分布式环境下,如何保证mysql主键的唯一性?

在分布式系统里,要保证MySQL主键的唯一性,说白了,就是不能再完全依赖MySQL自身那个简单的

AUTO_INCREMENT
机制了。我们得把生成唯一ID的逻辑从单体数据库里抽离出来,交给一个更“聪明”的、能跨节点协作的机制来处理。这通常意味着我们需要引入一个独立的分布式ID生成服务,或者采用某种算法,让每个服务实例都能独立生成全局唯一的ID。核心思路就是,让ID的生成不再是某个数据库实例的“私事”,而是整个分布式系统共同维护的“公事”。

解决方案

解决分布式环境下MySQL主键唯一性问题,我的经验是,没有银弹,只有最适合你业务场景的方案。但通常我们会从几个维度去思考:

首先,最直接也最容易想到的,是全局唯一ID生成器。这东西听起来有点玄乎,其实就是个服务,它的唯一职责就是吐出不重复的ID。比如,你可以用Redis的

INCR
命令来做,简单粗暴,但性能和可用性得靠Redis集群来保障。更高级一点的,像Twitter的Snowflake算法,它通过组合时间戳、机器ID和序列号来生成一个64位的长整型ID,既能保证唯一性,又能大致保持递增,对数据库索引很友好。还有一些基于数据库号段模式的方案,比如美团的Leaf,它会提前从数据库取一批ID号段到内存里,用完了再去取下一批,这样既减少了数据库压力,又保证了ID的递增和唯一。

其次,如果你对ID的递增性要求没那么高,或者说,你更看重去中心化和实现简单,那UUID(Universally Unique Identifier)就是个不错的选择。UUID是128位的,理论上碰撞的概率几乎为零,每个服务实例都能独立生成,不需要任何协调。缺点嘛,就是它是个字符串,比较长,而且无序,对数据库索引和存储都不太友好。但如果你的业务场景允许,比如日志ID、消息ID这种,UUID就挺香的。

最后,如果你真的想在数据库层面做点文章,也不是完全没辙,但会比较折腾。比如,你可以设置MySQL的

auto_increment_increment
auto_increment_offset
,让不同的数据库实例生成不同步长的自增ID。比如,实例A生成1, 3, 5...,实例B生成2, 4, 6...。但这只能解决两个或少数几个实例的冲突,而且要求你对每个实例的配置都了如指掌,维护起来挺麻烦的,扩展性也不好。在我看来,这更像是对现有单库架构的一种修补,而不是真正的分布式解决方案。

所以,综合来看,我个人更倾向于引入独立的分布式ID生成服务,尤其是像Snowflake或号段模式这种,它们在性能、可用性、ID特性(递增性)上做到了很好的平衡,能优雅地解决分布式环境下的主键唯一性问题。

为什么MySQL自带的AUTO_INCREMENT在分布式环境下会失效?

这个问题,说白了就是

AUTO_INCREMENT
的“管辖范围”太小了。它从设计之初就是为了在一个单体MySQL实例内部保证主键的唯一性。当你只用一台MySQL服务器时,每次插入一条新记录,它都会乖乖地把上一个ID加1,然后把新的ID给你,这个过程是严格串行的,所以绝对不会重复。

但一旦你进入了分布式环境,比如你做了数据库分库分表,或者有多个应用实例同时往不同的MySQL实例里写数据,问题就来了。每个MySQL实例都有它自己独立的

AUTO_INCREMENT
计数器,它们各自从1开始递增。

举个例子: 假设你有两个MySQL实例A和B,都有一张

users
表,并且
id
字段都是
AUTO_INCREMENT

  • 应用1连接到实例A,插入一条记录,得到ID=1。
  • 应用2连接到实例B,插入一条记录,也得到ID=1。
  • 应用1再次插入,得到ID=2。
  • 应用2再次插入,得到ID=2。

你看,这下就乱套了。如果这两个实例的数据最终需要合并,或者通过某种方式被同一个业务逻辑查询到,那么ID为1和ID为2的记录就出现了冲突。它们可能代表了不同的用户,但却拥有相同的唯一标识。这在业务上是灾难性的。

即使你尝试用

auto_increment_increment
auto_increment_offset
这种方式来“错开”ID,比如实例A从1开始,步长为2(1, 3, 5...),实例B从2开始,步长为2(2, 4, 6...),虽然能保证两个实例生成的ID不冲突,但这种方案的扩展性非常差。如果你要加第三个实例,第四个实例,你就要重新调整所有实例的配置,而且步长会越来越大,ID的密度也会降低。更关键的是,这种方式仍然是基于数据库实例的配置,而不是一个全局的、动态的ID生成机制。一旦某个实例挂了,或者需要扩容,整个ID生成体系就可能需要重新设计和调整,维护成本非常高。所以,对于真正的分布式系统来说,这种方案基本上是不可行的。

Snowflake算法是如何保证分布式ID唯一性的?它的优缺点是什么?

Snowflake算法,是Twitter开源的一个分布式ID生成算法,它巧妙地利用了时间、机器ID和序列号的组合,来生成一个64位的长整型ID。这东西在很多大型互联网公司都有广泛应用,因为它既能保证唯一性,又能大致保持ID的递增,对数据库索引和数据排序都非常友好。

它是怎么保证唯一性的呢?

Snowflake ID的结构通常是这样的:

宣小二
宣小二

宣小二:媒体发稿平台,自媒体发稿平台,短视频矩阵发布平台,基于AI驱动的企业自助式投放平台。

下载
  • 1位符号位(Sign Bit): 永远是0,因为ID是正数。
  • 41位时间戳(Timestamp): 精确到毫秒,可以支持大约69年的时间。这个时间戳通常是相对于一个“纪元”(Epoch)的,比如2015年1月1日0点0分0秒,这样可以减少时间戳的位数。
  • 10位机器ID(Worker ID): 这10位可以拆分成5位数据中心ID(DataCenter ID)和5位工作机器ID(Worker ID)。这样就能支持32个数据中心,每个数据中心32台机器,总共1024台机器。
  • 12位序列号(Sequence Number): 每毫秒内,每个机器可以生成4096个(2^12)不同的ID。如果一毫秒内生成的ID超过了这个数量,就等待下一毫秒再生成。

你看,这个组合就非常精妙了:

  1. 时间戳确保了ID的大致递增性。时间往前走,ID自然就变大。
  2. 机器ID确保了在同一毫秒内,不同机器生成的ID是唯一的。即使两个机器在同一毫秒内生成ID,由于它们的机器ID不同,最终的ID也会不同。
  3. 序列号确保了在同一毫秒内,同一台机器生成的ID是唯一的。一台机器在一毫秒内可能会有多个请求要生成ID,序列号就派上用场了。

这三者一组合,就几乎不可能出现重复ID了。

它的优缺点是什么?

优点:

  • 高性能、低延迟: ID的生成完全在内存中进行,不需要访问数据库或网络,生成速度非常快。
  • ID递增: 由于包含了时间戳,生成的ID是大致递增的,这对于MySQL的主键索引(B+树)非常友好,可以减少页分裂,提高插入性能。同时,按时间排序也很方便。
  • 高可用: 只要你的ID生成服务部署在多台机器上,并且每台机器都有唯一的Worker ID,就不会出现单点故障。
  • 无中心化协调(部分): 一旦Worker ID分配好,每台机器就可以独立生成ID,不需要实时进行中心化协调。
  • 信息量: ID中包含了时间信息,在排查问题时,可以通过ID大致推断出记录的生成时间。

缺点:

  • 时钟回拨问题: 如果服务器的时钟发生回拨(比如系统时间被手动调回),可能会生成重复的ID。通常的解决方案是,检测到时钟回拨时,要么等待时钟追上,要么直接报错。
  • Worker ID分配与管理: 如何为每台机器分配一个全局唯一的Worker ID是个挑战。你可以手动配置,也可以通过ZooKeeper、Redis等来动态管理。这需要一定的运维成本。
  • 序列号溢出: 理论上,如果一毫秒内ID生成请求超过4096个,就需要等待下一毫秒。在高并发场景下,这可能会导致短暂的延迟。不过,对于绝大多数业务来说,每毫秒4096个ID已经足够用了。
  • 位数限制: 64位长整型,虽然够用,但如果你的业务ID需要更长的位数,或者需要更精细的时间粒度,可能需要调整位数分配。

总的来说,Snowflake算法是一个非常成熟且实用的分布式ID解决方案,它的优点远大于缺点,非常适合需要高性能、递增ID的分布式系统。

除了Snowflake,还有哪些常见的分布式ID生成方案?它们各自适用于什么场景?

除了Snowflake,分布式ID生成方案还有不少,每种都有其独特的适用场景和权衡。

1. UUID(Universally Unique Identifier)

  • 工作原理: UUID是一个128位的数字,通常表示为32个十六进制字符,由五个部分组成,形式为
    8-4-4-4-12
    。UUID有多种版本(v1-v5),其中v1基于MAC地址和时间戳,v4是完全随机数。我们通常说的UUID更多指的是v4,即纯随机生成。
  • 优点:
    • 极高的唯一性: 随机生成的UUID碰撞概率极低,几乎可以忽略不计。
    • 完全去中心化: 每个服务实例都可以独立生成UUID,无需任何协调,没有单点故障风险。
    • 实现简单: 大多数编程语言都内置了生成UUID的库函数。
  • 缺点:
    • 无序性: UUID是无序的字符串,作为MySQL主键会导致索引树频繁分裂和重平衡,严重影响数据库插入和查询性能。
    • 存储和传输效率低: 128位的字符串比64位长整型占用更多存储空间,在网络传输时也更耗带宽。
    • 不友好: UUID对人类来说不直观,不方便记忆或口头传递。
  • 适用场景:
    • 对ID的递增性、排序性没有要求,且对性能要求不是极致的场景。
    • 需要完全去中心化生成ID,避免任何协调开销的场景,比如日志ID、消息ID、临时文件ID等。
    • 当数据库主键并非唯一的查询条件,或者数据量不大时,UUID也是一个简单省事的选择。

2. 数据库号段模式(Segment Mode,如美团Leaf)

  • 工作原理: 这种方案的核心思想是,由一个中心化的数据库(或者Redis)来维护ID的当前最大值和步长。各个应用服务在启动时或当当前号段用完时,向这个中心服务申请一个ID号段(比如从1000到2000)。拿到号段后,应用服务就可以在本地内存中自行生成ID,直到号段用完,再去申请下一个。
  • 优点:
    • 高性能: ID生成大部分在应用本地内存中完成,速度非常快。
    • ID递增: 保证了ID的递增性,对数据库索引友好。
    • 高可用: 中心化的ID服务可以做主从复制、集群部署,保证高可用。
  • 缺点:
    • 中心化依赖: 依然依赖一个中心化的服务(数据库或Redis),虽然可以通过集群解决单点问题,但仍然是瓶颈点。
    • 号段浪费: 如果某个服务申请了一个号段,但还没用完就挂了,那么这个号段里未使用的ID就会被浪费掉。
    • 网络开销: 申请号段时需要进行网络通信。
  • 适用场景:
    • 对ID的递增性有强要求,同时对性能和可用性要求都很高的场景。
    • 业务量较大,需要频繁生成ID,但不希望每次都访问数据库的场景。
    • 能够接受一定的中心化依赖和运维复杂度的团队。

3. Redis自增ID

  • 工作原理: 利用Redis的
    INCR
    INCRBY
    命令,每次调用就返回一个自增的ID。
  • 优点:
    • 性能极高: Redis是内存数据库,
      INCR
      操作非常快。
    • 实现简单: 只需要一行命令就能搞定。
    • ID递增: 保证了严格的递增。
  • 缺点:
    • Redis是单点: 虽然Redis可以做集群,但如果整个Redis集群挂掉,ID生成服务就不可用了。
    • 持久化风险: 如果Redis没有开启AOF或RDB持久化,或者持久化策略不当,Redis重启后ID可能会重置,导致重复。
    • 无业务含义: 生成的ID只是一个纯粹的数字,不包含任何业务或时间信息。
  • 适用场景:
    • 对ID要求极其简单,只要递增且唯一,对Redis的可用性有高度信任的场景。
    • 业务场景对ID的生成速度有极高要求,且能容忍Redis可能存在的持久化风险。
    • 已经广泛使用Redis作为核心组件的系统。

在我看来,选择哪种方案,最终还是得看你的具体业务需求、团队技术栈和运维能力。没有最好的,只有最适合的。如果你追求极致的性能和递增性,且能接受一定的运维成本,Snowflake或号段模式是很好的选择。如果你追求简单、去中心化,且对ID的无序性不敏感,UUID也能胜任。而Redis自增ID则更适合那些已经深度依赖Redis,且对ID生成要求不复杂的场景。

相关专题

更多
mysql修改数据表名
mysql修改数据表名

MySQL修改数据表:1、首先查看数据库中所有的表,代码为:‘SHOW TABLES;’;2、修改表名,代码为:‘ALTER TABLE 旧表名 RENAME [TO] 新表名;’。php中文网还提供MySQL的相关下载、相关课程等内容,供大家免费下载使用。

664

2023.06.20

MySQL创建存储过程
MySQL创建存储过程

存储程序可以分为存储过程和函数,MySQL中创建存储过程和函数使用的语句分别为CREATE PROCEDURE和CREATE FUNCTION。使用CALL语句调用存储过程智能用输出变量返回值。函数可以从语句外调用(通过引用函数名),也能返回标量值。存储过程也可以调用其他存储过程。php中文网还提供MySQL创建存储过程的相关下载、相关课程等内容,供大家免费下载使用。

246

2023.06.21

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

281

2023.07.18

mysql密码忘了怎么查看
mysql密码忘了怎么查看

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql密码忘了怎么办呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

514

2023.07.19

mysql创建数据库
mysql创建数据库

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql怎么创建数据库呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

255

2023.07.25

mysql默认事务隔离级别
mysql默认事务隔离级别

MySQL是一种广泛使用的关系型数据库管理系统,它支持事务处理。事务是一组数据库操作,它们作为一个逻辑单元被一起执行。为了保证事务的一致性和隔离性,MySQL提供了不同的事务隔离级别。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

386

2023.08.08

sqlserver和mysql区别
sqlserver和mysql区别

SQL Server和MySQL是两种广泛使用的关系型数据库管理系统。它们具有相似的功能和用途,但在某些方面存在一些显著的区别。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

530

2023.08.11

mysql忘记密码
mysql忘记密码

MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。那么忘记mysql密码我们该怎么解决呢?php中文网给大家带来了相关的教程以及其他关于mysql的文章,欢迎大家前来学习阅读。

599

2023.08.14

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
MySQL 教程
MySQL 教程

共48课时 | 1.8万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 804人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号