拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 UUID 当 MySQL 主键,上线半年后为什么写入性能掉一半?

用 UUID 当 MySQL 主键,上线半年后为什么写入性能掉一半?

用 UUID 当 MySQL 主键,上线半年后为什么写入性能掉一半?

很多人觉得分布式场景下直接用 UUID 当数据库主键省事,既不用考虑自增 ID 耗尽,也不用担心分库分表时的冲突。

如果你把它用在 MySQL 的 InnoDB 引擎里,刚上线可能没什么感觉。但几个月后数据量稍微起来一点,写入性能就会明显下降,甚至频发慢 SQL 报警。

这其实和 InnoDB 的底层设计有关。

聚簇索引的脾气

InnoDB 表的数据是按主键顺序存放在 B+ 树的叶子节点上的,这就是聚簇索引。

当你插入一条数据时,InnoDB 会根据主键的值找到合适的位置。如果主键是趋势递增的(比如自增 ID 或者雪花算法),新数据总是一条条往后追加。一个默认 16KB 的数据页写满了,就开一个新页。这个过程非常顺畅,磁盘主要是顺序写入。

但 UUID 完全不同,它是无序的。
上一条主键可能是550e8400...,下一条突然变成了1a2b3c4d...。
这就导致新插入的数据无法顺序追加,InnoDB 被迫去 B+ 树的中间某个数据页寻找位置。

页分裂与碎片陷阱

假设系统找到了那个应该插入 UUID 的数据页,但很不巧,这个 16KB 的页已经塞满了。这就引发了麻烦的“页分裂”。

InnoDB 必须申请一个新的数据页,把满页里大约一半的数据搬过去,再把新数据插进去。这里有两个代价:

第一是产生碎片。原来紧凑的 16KB 数据页被硬生生切成了两半,空间利用率瞬间掉到 50% 左右。如果持续乱序插入,表里会充斥着大量半空的碎片页。

第二是随机 I/O。为了维护树的平衡,树结构被频繁调整。原本只要在文件尾部写数据,现在变成了在磁盘上到处乱跳的随机写入。

如果业务并发量大,这种频繁的页分裂会让 MySQL 把大量的 CPU 和磁盘 I/O 耗在调整 B+ 树结构上,直观表现就是写入延迟飙升。

不光是写得慢。由于数据在磁盘上被打散,那些本来能利用局部性原理提升效率的范围查询(比如按创建时间捞数据),也因为要在不同的物理数据页之间跳跃,变得慢得出奇。

怎么解决?

分布式主键确实是刚需,但没必要死磕 UUID 字符串。

方案一:换用雪花算法(Snowflake)
这是目前最通用的做法。雪花算法生成的 64 位整数,高位是时间戳,低位是机器码和序列号。它不仅全网唯一,而且在时间上是趋势递增的。对 InnoDB 来说,它长得很像自增 ID,能规避页分裂问题。8 字节的 bigint 比 36 字节的 UUID 字符串省空间得多,每一页能存的索引和数据更多,树的高度更低,查询更快。

方案二:必须用 UUID 时做个小转换
有些老旧系统或者特殊场景非得用 UUID 不可。如果你用的 MySQL 8.0 以上版本,可以用UUID_TO_BIN()函数把 36 字节的 UUID 字符串转成 16 字节的 BINARY。更关键的是,它可以把 UUID 里代表时间戳的位交换到前面,硬生生把乱序的 UUID 变成趋势递增的值。

分布式唯一性很重要,但顺着数据库的底层机制顺毛摸更重要。下回建表再遇到 UUID 主键,记得先拦下来。

返回列表