一文详解为什么MySQL不推荐使用雪花和UUID做主键

 更新时间:2026年05月26日 09:51:48   作者:天天摸鱼的java工程师  
这篇文章主要介绍了为什么MySQL不推荐使用雪花和UUID做主键的相关资料,本文从MySQL索引机制、数据存储原理、性能影响等角度解析,指出UUID和Snowflake做主键存在存储空间浪费、索引碎片化、查询性能低下等缺陷,需要的朋友可以参考下

前言

在 MySQL 表设计中,主键选择是影响性能的关键因素。雪花 ID(Snowflake)和 UUID 作为分布式系统中常用的唯一标识符,虽能解决全局唯一性问题,但在 MySQL 中却并非主键的最佳选择。本文将从 MySQL 索引机制、数据存储原理、性能影响等角度,解析为何不推荐使用这两种 ID 作为主键。

一、MySQL 主键的设计核心:聚簇索引与数据组织

1. 聚簇索引的本质

MySQL 的 InnoDB 存储引擎采用聚簇索引(Clustered Index) ,即数据行的物理存储顺序与主键索引的顺序一致。这意味着:

  • 主键不仅是数据的唯一标识,更是数据在磁盘上的存储顺序依据。
  • 非主键索引(二级索引)存储的是主键值而非数据地址,查询时需通过主键回表。

2. 理想主键的特性

为了优化聚簇索引的性能,理想的主键应具备:

  1. 固定长度:便于快速定位数据页
  1. 顺序增长:新数据插入时按顺序追加,减少页分裂
  1. 紧凑存储:占用磁盘空间小,提升索引缓存效率

二、UUID 做主键的四大性能缺陷

1. 存储长度大,浪费磁盘空间

  • UUID 格式:36 位字符串(如550e8400-e29b-41d4-a716-446655440000)
  • 存储开销
    • CHAR(36):占用 36 字节(定长存储)
    • VARCHAR(36):占用 36+1 字节(变长存储)
  • 对比自增主键
    • BIGINT UNSIGNED:仅占 8 字节,节省 78% 空间

2. 无序性导致索引碎片化

  • 插入模式:UUID 是随机生成的字符串,插入时数据页位置随机分布
  • B + 树分裂

    • 随机插入导致数据页频繁分裂(Page Split),降低写入性能
    • 碎片化索引增加查询时的 I/O 次数

3. 字符串比较效率低下

  • 索引查询:UUID 的字符串比较需逐个字符对比(如字典序)
  • 范围查询:无法利用主键的有序性优化(如BETWEEN查询)
  • 性能数据:UUID 主键的查询速度比自增主键慢 30% 以上(实测数据)

4. 影响二级索引性能

  • 二级索引存储 UUID 主键值,导致:
    • 索引条目变大,降低缓存命中率
    • 回表查询时需多次随机 I/O(UUID 的无序性加剧此问题)

三、雪花 ID 的优化与局限性

1. 雪花 ID 的改进点

  • 数据类型:长整型(8 字节),比 UUID 更紧凑
  • 有序性:时间戳 + 工作进程 ID,保证趋势递增
  • 示例结构
1bit(符号位)+41bit(时间戳)+10bit(工作节点)+12bit(序列号)

2. 仍存在的问题

(1)非完全顺序性

  • 同一毫秒内生成的 ID 顺序由序列号决定,可能产生 "小范围逆序"
  • 跨工作节点时,ID 顺序可能跳跃,导致轻微的页分裂

(2)分布式场景的隐藏成本

  • 需要独立的 ID 生成服务(如雪花算法的工作节点管理)
  • 时钟回退问题:服务器时间回退可能导致 ID 重复

(3)扩容困难

  • 工作节点 ID 的位数限制(如 10bit 最多支持 1024 个节点)
  • 节点重启后可能导致 ID 不连续

四、MySQL 主键的最佳实践:自增 ID vs 分布式 ID

1. 单机场景:自增主键(AUTO_INCREMENT)

优势:

  • 顺序插入:数据页按主键顺序追加,无页分裂开销
  • 存储高效:INT UNSIGNED占 4 字节,BIGINT UNSIGNED占 8 字节
  • 性能最优:插入速度比 UUID 快 50% 以上(InnoDB 实测)

实现方式:

CREATE TABLE `users` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(50) NOT NULL,
  ...
) ENGINE=InnoDB;

2. 分布式场景:推荐组合方案

方案 1:自增主键 + 分库分表

  • 水平拆分:按业务维度(如用户 ID 模 1024)分到不同数据库
  • 主键规则:每个库的自增起始值不同(如库 1 从 1 开始,库 2 从 1000001 开始)

方案 2:使用 MySQL 原生分布式 ID(如 8.0 + 的 SERVER_UUID)

-- 生成128位UUID(优化存储为16字节二进制)
SET @uuid = UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000');
INSERT INTO `table` (`id`) VALUES (@uuid);
-- 查询时转换为字符串
SELECT BIN_TO_UUID(`id`) FROM `table`;

方案 3:引入分布式 ID 生成器(如百度 UidGenerator、美团 Leaf)

  • 核心逻辑
    • 基于数据库号段模式(预先分配 ID 段)
    • 结合雪花算法生成趋势递增 ID
  • 优势:兼顾有序性与分布式扩展

五、不得不使用 UUID/Snowflake 的应对策略

1. 优化存储格式

  • 改用二进制存储:将 UUID 转换为 16 字节的二进制数据(BINARY(16))
-- 插入时转换
INSERT INTO `table` (`uuid_bin`) VALUES (UUID_TO_BIN(UUID()));
-- 查询时转换
SELECT BIN_TO_UUID(`uuid_bin`) FROM `table`;
  • 空间节省:存储长度从 36 字节降至 16 字节,提升索引效率

2. 强制顺序化(仅适用于雪花 ID)

  • 按时间戳排序:在应用层对 ID 进行排序后再插入
  • 缺点:高并发下可能导致插入阻塞

3. 非聚簇索引表(牺牲一致性换性能)

CREATE TABLE `non_clustered_table` (
  `uuid` CHAR(36) PRIMARY KEY,
  `data` TEXT,
  INDEX `idx_data` (`data`)
) ENGINE=InnoDB DISABLE_KEY_CACHE=1;
  • 原理:关闭聚簇索引,强制使用二级索引(不推荐,违背 InnoDB 设计初衷)

六、性能对比实测(InnoDB 引擎,100 万条数据)

主键类型插入速度(条 / 秒)主键索引大小范围查询耗时(SELECT * FROM t WHERE id < 10000)
自增 ID(BIGINT)12,3458.2MB12ms
雪花 ID(BIGINT)10,8908.2MB15ms
UUID(CHAR(36))6,54338.5MB28ms

测试环境:MySQL 8.0,4 核 8GB,SSD 磁盘

七、总结:主键选择的核心原则

  1. 优先遵循聚簇索引优化:利用自增 ID 的顺序性提升写入性能
  1. 空间优先原则:避免使用长字符串作为主键(如 UUID)
  1. 分布式场景权衡
    • 轻量级场景:自增 ID + 分库分表
    • 强一致性场景:雪花 ID(牺牲部分性能)
  1. 避免过度设计:单机场景无需引入分布式 ID,自增主键已足够高效

MySQL 的主键设计本质上是在数据唯一性、查询性能、存储效率之间的权衡。除非有明确的分布式唯一性需求,否则应优先选择自增整数作为主键。对于必须使用雪花 ID 或 UUID 的场景,需通过二进制存储、顺序化插入等手段尽可能减少性能损耗。

到此这篇关于MySQL不推荐使用雪花和UUID做主键的文章就介绍到这了,更多相关MySQL不推荐使用雪花和UUID做主键内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL 中的 JSON_UNQUOTE 与 JSON_EXTRACT 使用示例详解

    MySQL 中的 JSON_UNQUOTE 与 JSON_EXTRACT&nbs

    文章详细介绍了MySQL中的JSON_EXTRACT和JSON_UNQUOTE函数,包括它们的功能、参数、返回值以及适用场景,通过示例展示了如何提取嵌套对象字段和数组元素,并建议使用IFNULL()或COALESCE()处理路径不存在的情况,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • MySQL中INSERT+SELECT的使用方式

    MySQL中INSERT+SELECT的使用方式

    MySQL的INSERT INTO SELECT FROM语句允许用户通过一条SQL语句实现从一个或多个表中查询数据并将结果插入到另一个表中,这种方式特别适用于需要将数据从一张表迁移到另一张表,或者基于多表查询结果创建新表的场景
    2024-10-10
  • Mysql查询所有表和字段信息的方法

    Mysql查询所有表和字段信息的方法

    这篇文章主要介绍了Mysql查询所有表和字段信息,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • 一起了解了解MySQL存储引擎

    一起了解了解MySQL存储引擎

    大家好,本篇文章主要讲的是一起了解了解MySQL存储引擎,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • 图文详解Mysql中如何查看Sql语句的执行时间

    图文详解Mysql中如何查看Sql语句的执行时间

    写程序的人往往需要分析所写的SQL语句是否已经优化过了,服务器的响应时间有多快,所以下面这篇文章主要给大家介绍了关于Mysql中如何查看Sql语句的执行时间的相关资料,需要的朋友可以参考下
    2021-12-12
  • DDL数据库与表的创建和管理深入讲解使用教程

    DDL数据库与表的创建和管理深入讲解使用教程

    这篇文章主要介绍了DDL数据库与表的创建和管理,系统架构的层面来看,数据库从大到小依次是数据库服务器(上面安装了DBMS和数据库)、数据库(也称database或者schema)、数据表、数据表的行与列
    2023-04-04
  • mysql 5.7.17 64bit安装配置方法图文教程

    mysql 5.7.17 64bit安装配置方法图文教程

    这篇文章主要为大家详细介绍了mysql 5.7.17 64bit解压缩版安装配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-01-01
  • 汇总整理MYSQL相关操作命令

    汇总整理MYSQL相关操作命令

    本文汇总了一些常用的mysql命令。
    2009-04-04
  • MySQL Semaphore wait has lasted使用详解

    MySQL Semaphore wait has lasted使用详解

    MySQL 5.7.19 Semaphore wait >600秒错误源于InnoDB线程等待信号量超时,常见于死锁、资源竞争或IO瓶颈,排查需检查长事务、高并发写入、磁盘性能及数据页损坏,建议升级至5.7或8.0版本以修复问题
    2025-07-07
  • Mysql如何查询某条记录在分页的第几页详析

    Mysql如何查询某条记录在分页的第几页详析

    查询是我们日常工作中经常会遇到的一个功能,下面这篇文章主要给大家介绍了关于Mysql如何查询某条记录在分页的第几页的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-11-11

最新评论