首页 > 项目 > 当前页面

MySQL主键是有序的好,还是无序的好?

2026-06-25 NEW个对象

📌 MySQL主键是有序的好,还是无序的好?

🎯 结论:

对于 InnoDB 存储引擎而言,主键尽量选择有序(递增),例如 AUTO_INCREMENT、Snowflake 趋势递增 ID,而不是 UUID 这种完全随机的主键。

B+Tree 本质上就是一棵有序多叉搜索树,ID逐渐增大,只需要在末尾添加就可以了,InnoDB 的数据是按照主键顺序组织存储(Clustered Index),主键是否有序,将直接影响:

  • B+Tree 是否频繁分裂
  • 磁盘页是否连续写入
  • 缓存命中率
  • 插入性能
  • 磁盘碎片
  • 整体查询效率

1️⃣ 问题背景

很多开发人员设计表结构时,都会纠结一个问题:

  • 使用数据库自增ID?
  • 使用UUID?
  • 使用雪花ID?
  • 使用业务ID?

很多人认为主键只是唯一标识即可,其实对于 InnoDB 来说,主键不仅仅是唯一索引,它还是整个数据文件的存储顺序,因此主键设计直接影响数据库性能。

💡 很多人优化SQL、优化索引,却忽略了主键设计,这是影响MySQL性能的重要因素之一。

2️⃣ 核心原理

InnoDB 采用的是聚簇索引(Clustered Index)

所谓聚簇索引,就是:

  • 索引和数据放在一起
  • 整张表按照主键顺序存储
  • 叶子节点保存完整的数据记录
InnoDB ``` Root │ ``` ┌──────┴──────┐ │ │ Internal Internal │ │ ┌─┴─┐ ┌───┴───┐ Leaf Leaf Leaf Leaf Leaf: ID=1 ID=2 ID=3 ID=4 ......

因此,主键决定了数据在磁盘上的排列方式。

3️⃣ 数据结构分析

✅ 有序主键(AUTO_INCREMENT)

1 2 3 4 5 6 7 8 9 10 11 12

插入数据时,总是在最后一个数据页追加。

特点:

  • 几乎不会发生页分裂
  • 磁盘顺序写
  • 缓存命中率高
  • 插入效率最高

❌ 无序主键(UUID)

550e8400... 12fdab... 9c8ef... 34ab... cddf...

UUID 每次都会随机插入到 B+Tree 的任意位置。

可能插入到第一页,也可能插入到最后一页。

这样会导致:

  • 页分裂(Page Split):当 B+Tree 的某个数据页已经满了,又要往里面插入新数据时,数据库不得不新申请一个数据页,把原来页中的一部分数据移动过去,然后更新 B+Tree 的结构。
  • 页移动
  • 大量随机IO:页分裂和页移动会导致Buffer pool失效,从而增加缓存失效
  • 磁盘碎片增加:因为页分裂会产生新页面,页2分裂的数据到页30,不连续导致磁盘碎片
  • 缓存污染:

4️⃣ 算法分析

下面分别看看两种插入方式。

有序主键插入

已有: 1 2 3 4 5 插入: 6 结果: 1 2 3 4 5 6

整个过程只需要找到最后一个叶子节点即可。

无序主键插入

已有: 10 20 30 40 50 插入: 25 结果: 10 20 25 30 40 50

如果当前页已满,则需要执行:

  • 申请新的数据页
  • 移动一半数据
  • 修改父节点
  • 重新维护 B+Tree

这就是经典的 Page Split(页分裂)

5️⃣ 执行流程

有序主键 插入数据 │ ▼ 定位最后一个叶子节点 │ ▼ 追加写入 │ ▼ 结束 无序主键 插入数据 │ ▼ 随机定位叶子节点 │ ▼ 数据页已满? │ ┌────┴────┐ │ │ 否 是 │ │ ▼ ▼ 写入 页分裂 │ ▼ 移动数据 │ ▼ 修改父节点 │ ▼ 完成插入

6️⃣ 实际案例

案例一:订单表

CREATE TABLE orders( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT, amount DECIMAL(10,2) );

每天新增几十万订单。

由于 ID 递增,所有数据都会写到最后一个页,插入效率非常高。

案例二:UUID 主键

CREATE TABLE orders( id CHAR(36) PRIMARY KEY, ...... );

UUID 每次都会随机写入,随着数据越来越多:

  • 索引越来越碎
  • 页分裂越来越频繁
  • Buffer Pool 命中率下降
  • 写入性能明显降低
⚠️ 注意:

UUID 并不是不能使用,而是不建议直接作为 InnoDB 聚簇主键。

如果业务必须使用 UUID,可以考虑:
  • 增加一个 BIGINT 自增主键作为聚簇索引
  • UUID 建唯一索引
  • 使用趋势递增的 Snowflake ID、UUID v7、ULID 等方案,降低随机写带来的影响

7️⃣ 优缺点分析

对比项 有序主键 无序主键
插入性能 ★★★★★ ★★☆☆☆
页分裂 极少 频繁
随机IO
磁盘碎片
缓存命中率
推荐程度 ★★★★★ ★★☆☆☆

8️⃣ 面试常见问题

Q1:为什么 InnoDB 推荐使用自增主键?

因为自增主键始终向 B+Tree 尾部追加写入,能够减少页分裂、降低随机 IO,提高 Buffer Pool 命中率,使插入性能更高。

Q2:UUID 为什么性能差?

UUID 完全随机,每次插入都会定位不同的数据页,容易导致页分裂、数据移动、索引碎片和随机磁盘访问,因此写入性能明显下降。

Q3:雪花ID是否属于有序主键?

Snowflake ID 整体呈趋势递增,虽然不是严格连续,但对于 InnoDB 来说仍然属于较优的主键方案,能够显著减少随机写带来的影响。

Q4:什么时候可以使用 UUID?

当系统需要全局唯一、离线生成、跨库合并或避免暴露业务规模时,可以使用 UUID,但建议不要直接作为聚簇主键,而是作为唯一业务键使用。

9️⃣ 总结

  • ✅ InnoDB 数据按照聚簇主键顺序存储,主键设计会影响整张表的组织方式。
  • ✅ 有序主键采用顺序追加写入,可减少页分裂、降低随机 IO,并提升写入性能。
  • ✅ 无序主键会导致频繁页分裂、索引碎片增多以及缓存命中率下降。
  • ✅ 自增 ID、Snowflake ID、UUID v7、ULID 等趋势递增方案,更适合作为聚簇主键。
  • ✅ 如果业务必须使用 UUID,建议额外增加 BIGINT 自增主键作为聚簇索引,将 UUID 建立唯一索引,以兼顾业务需求与数据库性能。

📌 MySQL主键是有序的好,还是无序的好?

🎯 结论:

对于 InnoDB 存储引擎而言,主键尽量选择有序(递增),例如 AUTO_INCREMENT、Snowflake 趋势递增 ID,而不是 UUID 这种完全随机的主键。

因为 InnoDB 的数据是按照主键顺序组织存储(Clustered Index),主键是否有序,将直接影响:
  • B+Tree 是否频繁分裂
  • 磁盘页是否连续写入
  • 缓存命中率
  • 插入性能
  • 磁盘碎片
  • 整体查询效率

1️⃣ 问题背景

很多开发人员设计表结构时,都会纠结一个问题:

  • 使用数据库自增ID?
  • 使用UUID?
  • 使用雪花ID?
  • 使用业务ID?

很多人认为主键只是唯一标识即可,其实对于 InnoDB 来说,主键不仅仅是唯一索引,它还是整个数据文件的存储顺序,因此主键设计直接影响数据库性能。

💡 很多人优化SQL、优化索引,却忽略了主键设计,这是影响MySQL性能的重要因素之一。

2️⃣ 核心原理

InnoDB 采用的是聚簇索引(Clustered Index)

所谓聚簇索引,就是:

  • 索引和数据放在一起
  • 整张表按照主键顺序存储
  • 叶子节点保存完整的数据记录
InnoDB ``` Root │ ``` ┌──────┴──────┐ │ │ Internal Internal │ │ ┌─┴─┐ ┌───┴───┐ Leaf Leaf Leaf Leaf Leaf: ID=1 ID=2 ID=3 ID=4 ......

因此,主键决定了数据在磁盘上的排列方式。

3️⃣ 数据结构分析

✅ 有序主键(AUTO_INCREMENT)

1 2 3 4 5 6 7 8 9 10 11 12

插入数据时,总是在最后一个数据页追加。

特点:

  • 几乎不会发生页分裂
  • 磁盘顺序写
  • 缓存命中率高
  • 插入效率最高

❌ 无序主键(UUID)

550e8400... 12fdab... 9c8ef... 34ab... cddf...

UUID 每次都会随机插入到 B+Tree 的任意位置。

可能插入到第一页,也可能插入到最后一页。

这样会导致:

  • 页分裂(Page Split)
  • 页移动
  • 大量随机IO
  • 磁盘碎片增加
  • 缓存污染

4️⃣ 算法分析

下面分别看看两种插入方式。

有序主键插入

已有: 1 2 3 4 5 插入: 6 结果: 1 2 3 4 5 6

整个过程只需要找到最后一个叶子节点即可。

无序主键插入

已有: 10 20 30 40 50 插入: 25 结果: 10 20 25 30 40 50

如果当前页已满,则需要执行:

  • 申请新的数据页
  • 移动一半数据
  • 修改父节点
  • 重新维护 B+Tree

这就是经典的 Page Split(页分裂)

5️⃣ 执行流程

有序主键 插入数据 │ ▼ 定位最后一个叶子节点 │ ▼ 追加写入 │ ▼ 结束 无序主键 插入数据 │ ▼ 随机定位叶子节点 │ ▼ 数据页已满? │ ┌────┴────┐ │ │ 否 是 │ │ ▼ ▼ 写入 页分裂 │ ▼ 移动数据 │ ▼ 修改父节点 │ ▼ 完成插入

6️⃣ 实际案例

案例一:订单表

CREATE TABLE orders( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT, amount DECIMAL(10,2) );

每天新增几十万订单。

由于 ID 递增,所有数据都会写到最后一个页,插入效率非常高。

案例二:UUID 主键

CREATE TABLE orders( id CHAR(36) PRIMARY KEY, ...... );

UUID 每次都会随机写入,随着数据越来越多:

  • 索引越来越碎
  • 页分裂越来越频繁
  • Buffer Pool 命中率下降
  • 写入性能明显降低
⚠️ 注意:

UUID 并不是不能使用,而是不建议直接作为 InnoDB 聚簇主键。

如果业务必须使用 UUID,可以考虑:
  • 增加一个 BIGINT 自增主键作为聚簇索引
  • UUID 建唯一索引
  • 使用趋势递增的 Snowflake ID、UUID v7、ULID 等方案,降低随机写带来的影响

7️⃣ 优缺点分析

对比项 有序主键 无序主键
插入性能 ★★★★★ ★★☆☆☆
页分裂 极少 频繁
随机IO
磁盘碎片
缓存命中率
推荐程度 ★★★★★ ★★☆☆☆

8️⃣ 面试常见问题

Q1:为什么 InnoDB 推荐使用自增主键?

因为自增主键始终向 B+Tree 尾部追加写入,能够减少页分裂、降低随机 IO,提高 Buffer Pool 命中率,使插入性能更高。

Q2:UUID 为什么性能差?

UUID 完全随机,每次插入都会定位不同的数据页,容易导致页分裂、数据移动、索引碎片和随机磁盘访问,因此写入性能明显下降。

Q3:雪花ID是否属于有序主键?

Snowflake ID 整体呈趋势递增,虽然不是严格连续,但对于 InnoDB 来说仍然属于较优的主键方案,能够显著减少随机写带来的影响。

Q4:什么时候可以使用 UUID?

当系统需要全局唯一、离线生成、跨库合并或避免暴露业务规模时,可以使用 UUID,但建议不要直接作为聚簇主键,而是作为唯一业务键使用。

9️⃣ 总结

  • ✅ InnoDB 数据按照聚簇主键顺序存储,主键设计会影响整张表的组织方式。
  • ✅ 有序主键采用顺序追加写入,可减少页分裂、降低随机 IO,并提升写入性能。
  • ✅ 无序主键会导致频繁页分裂、索引碎片增多以及缓存命中率下降。
  • ✅ 自增 ID、Snowflake ID、UUID v7、ULID 等趋势递增方案,更适合作为聚簇主键。
  • ✅ 如果业务必须使用 UUID,建议额外增加 BIGINT 自增主键作为聚簇索引,将 UUID 建立唯一索引,以兼顾业务需求与数据库性能。

  • UUID 作为主键时,由于键值随机,新记录会随机插入到 B+Tree 的不同叶子节点,而不是顺序追加到最后一个页。当目标页已满时,会触发页分裂,需要申请新页、迁移部分数据并更新父节点,增加了插入开销。同时,随机插入会导致访问的数据页分散,增加随机页访问和随机磁盘 IO 的概率;页分裂还会使物理页分配更加离散,增加磁盘碎片。另外,大量随机访问不同的数据页,会频繁替换 Buffer Pool 中的缓存页,降低缓存命中率,形成缓存污染。因此,InnoDB 通常推荐使用自增主键,以减少页分裂并提升写入性能。

相关文章

NEW个对象 NEW个对象
JAVA是世界上最好的语言