全局唯一的UUID为何大厂严禁用作主键?
一、问题提出
明明UUID具备全局唯一性,为何大厂却严禁将其用作数据库主键?这背后涉及数据库底层存储、性能优化等多方面的原理。
二、UUID不适合做主键的核心原因
1. 无序性与InnoDB存储引擎的冲突
UUID是完全无序的随机字符串,而MySQL的InnoDB存储引擎极度依赖顺序性。在高并发写入场景下,UUID的无序写入会引发:
- 随机IO:破坏了InnoDB基于B+树的顺序存储结构,导致磁盘IO操作随机且频繁。
- 页分裂(Page Split):当数据页写满时,强行插入乱序的UUID会触发InnoDB的页分裂操作,需要将原数据页的一半数据物理搬运到新页,这一过程会产生大量数据碎片,同时消耗大量系统资源。
2. 二级索引膨胀与内存消耗
InnoDB的二级索引叶子节点存储的是主键的值而非物理地址。UUID作为字符串(通常36字节),相比传统的BIGINT(8字节),会使二级索引体积膨胀4.5倍。这会引发连锁反应:
- 内存中能缓存的数据页数量断崖式下跌,频繁触发
cacheMiss,原本可在内存中极速完成的查询被迫转向磁盘,严重影响性能。 - 索引体积的膨胀还会增加磁盘空间占用和数据检索时间。
三、解决方案:Snowflake(雪花算法)
大厂普遍采用雪花算法来替代UUID作为分布式场景下的主键,其设计巧妙且契合InnoDB的存储特性:
1. 结构组成
雪花算法将一个64位的Long类型ID分为四部分:
- 最高位:固定为0,保证ID为正数。
- 时间戳(41位):以毫秒为单位,保证ID随时间趋势递增,完美契合B+树的顺序写入特性。
- 机器ID(10位):用于区分不同机器,解决多机并发冲突问题。
- 序列号(12位):支持单机一毫秒内生成多个ID,提升并发生成能力。
2. 优势
- 恢复顺序IO:彻底解决页分裂问题,写入性能极大提升。
- 内存友好:本质是8字节的数字,大幅降低二级索引的内存消耗,提高Buffer Pool的内存命中率。
- 分布式友好:纯本地内存生成ID,天然支持分库分表场景。
四、主键选型指南
| 类型 | 特点 | 适用场景 |
|---|---|---|
| UUID | 全局唯一,但存在随机IO、页分裂、内存膨胀等问题 | 仅适用于不建核心索引的流水号 |
| 自增ID | 极致紧凑,写入性能高,但易暴露业务单量,且不支持分布式场景 | 单机中小系统 |
| Snowflake | 兼顾底层存储物理特性和分布式业务需求,是高并发架构下的主力 | 分布式高并发系统 |
通过对UUID、自增ID和雪花算法的对比,我们可以根据业务场景和系统架构选择合适的主键生成策略,以保障系统的性能和可扩展性。