为什么推荐使用自增主键?使用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 三大优点
- 天然全局唯一:分库分表、多服务分布式架构下,各节点生成 ID 不会重复,不用依赖数据库发号。
- 防爬虫泄露业务体量 :自增 ID 容易被遍历,
/order/1、/order/2一眼看出订单量;UUID 无规律,安全性更高。 - 去中心化生成:应用层直接生成,不访问数据库,减轻 DB 压力。
UUID 五大硬伤(重点标红)
-
写入随机,页分裂和索引碎片严重
常规 UUID 随机分布,插入位置不可控,数据量大后碎片率极高,频繁
OPTIMIZE TABLE都很难修复。补充知识点 :UUID v1 带时间戳,是有序的,可以缓解页分裂。但它自带物理 MAC 地址,有隐私泄露风险,业务上极少使用。
-
字段体积大,索引膨胀严重
字符串 UUID 是 BIGINT 的 4 倍多,二级索引跟着一起胖,磁盘和内存占用双双上涨。
-
无法利用磁盘顺序 IO 和 CPU 缓存
主键无序,缓存命中率低。精准主键查询还行,范围查询和排序性能拉胯。
-
主键没有顺序业务价值
ORDER BY id对 UUID 来说完全是随机排序,不像自增 ID 能反映创建时间,没法靠 ID 做时间分页。 -
高并发写入拖累 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 损耗。