为什么推荐使用自增主键?使用UUID作为主键的优缺点是什么?

为什么推荐使用自增主键?使用UUID作为主键的优缺点是什么?


推荐自增主键的五大原因

先记住一个前提:InnoDB 是聚簇索引,整张表的数据就是按主键顺序存在 B+ 树叶子页里的。这是所有区别的根源。

1. 顺序追加写入,大幅减少页分裂

sql 复制代码
CREATE TABLE user_auto (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

自增 ID 持续递增,新数据永远追加在数据页末尾。页面填满后新开一页就行,几乎不会触发页分裂。

通俗比喻:就像往文件夹里按 1、2、3 的顺序放文件,永远往后塞;UUID 是随机往里插,文件夹撑破了就要拆分、挪数据,大量消耗磁盘 IO,产生索引碎片。

2. 页面填充率高,磁盘空间利用率好

顺序写入页面填充率接近 15/16;UUID 随机插入频繁页分裂,页面经常只填充一半,大量磁盘空间闲置浪费。

3. 主键连续,范围查询和排序性能极强

日常高频 SQL:id > 100 AND id < 200,或者 ORDER BY id DESC LIMIT 10。自增主键在磁盘上物理连续,走顺序 IO,直接扫过去。

UUID 没规律,ORDER BY uuid 变成随机 IO,磁盘头来回跳,性能差距明显。

4. 主键字段更小,二级索引整体瘦身,B+ 树更矮

BIGINT 只占 8 字节;字符串 UUID 占 36 字节,二进制 UUID 也要 16 字节。

InnoDB 所有二级索引的叶子节点都存主键值。主键越大,索引越胖。一张表 5 个二级索引,千万级数据可能多出几百 MB。

更重要的是:主键越小,B+ 树每层能放下的索引条目越多,树的高度就越低 。同样是千万级数据,8 字节主键可能树高 3 层,36 字节 UUID 可能树高 4 层。多一层就多一次磁盘 IO

5. 自增锁机制完善,高并发写入稳定

MySQL 默认 innodb_autoinc_lock_mode = 1,单行插入几乎无锁,批量插入也做了轻量优化,并发写入吞吐稳定。


UUID 做主键的优缺点

屏幕中栏贴两套建表 SQL,上半部分讲优点,下半部分标红讲缺点。

两种 UUID 建表写法

sql 复制代码
-- 普通字符串 UUID,可读性强,但最占空间
CREATE TABLE user_uuid_str (
    id CHAR(36) PRIMARY KEY,
    name VARCHAR(50)
) ENGINE=InnoDB;

-- MySQL 推荐二进制存储,省一半空间
CREATE TABLE user_uuid_bin (
    id BINARY(16) PRIMARY KEY,
    name VARCHAR(50)
) ENGINE=InnoDB;

UUID 三大优点

  1. 天然全局唯一:分库分表、多服务分布式架构下,各节点生成 ID 不会重复,不用依赖数据库发号。
  2. 防爬虫泄露业务体量 :自增 ID 容易被遍历,/order/1/order/2 一眼看出订单量;UUID 无规律,安全性更高。
  3. 去中心化生成:应用层直接生成,不访问数据库,减轻 DB 压力。

UUID 五大硬伤(重点标红)

  1. 写入随机,页分裂和索引碎片严重

    常规 UUID 随机分布,插入位置不可控,数据量大后碎片率极高,频繁 OPTIMIZE TABLE 都很难修复。

    补充知识点 :UUID v1 带时间戳,是有序的,可以缓解页分裂。但它自带物理 MAC 地址,有隐私泄露风险,业务上极少使用。

  2. 字段体积大,索引膨胀严重

    字符串 UUID 是 BIGINT 的 4 倍多,二级索引跟着一起胖,磁盘和内存占用双双上涨。

  3. 无法利用磁盘顺序 IO 和 CPU 缓存

    主键无序,缓存命中率低。精准主键查询还行,范围查询和排序性能拉胯。

  4. 主键没有顺序业务价值

    ORDER BY id 对 UUID 来说完全是随机排序,不像自增 ID 能反映创建时间,没法靠 ID 做时间分页。

  5. 高并发写入拖累 TPS

    随机写频繁刷新缓冲池,热点数据页来回换入换出,数据库吞吐明显下降。


选型决策树

使用场景 首选主键方案 选型逻辑
单机单库、不分片 BIGINT 自增 性能、空间、维护全最优
分布式、分库分表 雪花算法(Snowflake) 趋势递增 + 全局唯一,兼顾自增和 UUID 的优点
必须去中心化、不能依赖发号器 BINARY(16) UUID 二进制压缩,减少空间损耗
防爬、隐藏业务数据 UUID / 雪花 无规律 ID,防止批量遍历

如果必须用 UUID,至少做这个优化

sql 复制代码
-- 插入:字符串 UUID 转二进制,36 字节变 16 字节
INSERT INTO user_uuid_bin VALUES (UUID_TO_BIN(UUID()), 'Alice');

-- 查询:二进制转回可读字符串
SELECT BIN_TO_UUID(id), name FROM user_uuid_bin;

二者没有绝对优劣,核心看部署架构。单机优先自增,分布式优先雪花算法;UUID 只在强去中心化、隐私防爬时兜底使用,一定要用二进制压缩存储。


额外拓展

第一,线上禁止直接用 CHAR(36) UUID 做主键,实在要用必须 BINARY(16)

第二,分布式不要直接上 UUID,优先雪花算法、百度 UidGenerator 这类有序全局 ID

第三,所有隔离级别下,自增主键写入性能都稳定,UUID 则全级别都有 IO 损耗。

相关推荐
HXDGCL1 小时前
东莞市华创力科技:专业环形导轨工厂,助力自动化产线升级
运维·科技·自动化
麻瓜code1 小时前
【Redis 】数据类型、持久化与过期删除
数据库·redis·缓存
用户608186527901 小时前
一文吃透 Avalonia 布局容器|Grid 到 UniformGrid 核心用法 + 语法糖实战
后端
空杆推不起1 小时前
穿透 Flink CDC 表层用法:数据库日志捕获机制、Flink Source 运行时、端到端一致性底层原理详解
大数据·数据库·flink
小孔龙1 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化
Ai拆代码的曹操2 小时前
Dubbo 线程池爆满排坑:Linux 用户线程数限制,一个容易被忽略的根因
后端·源码阅读
殷忆枫2 小时前
Linux 4G模块驱动适配实战:从手动绑定到自动识别
linux·运维·服务器
知彼解己2 小时前
Eclipse Temurin:企业级 Java JDK 发行版的最佳实践
java·ide·eclipse
朱容zr3331332 小时前
请解释“回表”的概念。
java·前端·数据库