数据库中使用 UUID 主键的最佳实践
UUID 主键的优势
使用 UUID 作为数据库主键的核心优势:在分布式系统中,多个节点可以独立生成 ID 而不会冲突,无需全局锁或中央 ID 发生器;不暴露顺序,用户无法通过猜测 ID 枚举资源(提高安全性);便于数据迁移和合并,来自不同数据库的记录可以合并而不会 ID 冲突;可以在客户端预先生成 ID,减少一次数据库往返;在微服务架构中,各服务可以独立管理自己的 ID 空间。
UUID 主键的性能挑战
UUID v4 作为主键的主要性能问题在于 B-Tree 索引:由于 UUID v4 是完全随机的,每次插入新记录时,新 UUID 可能落在索引树的任意位置,而不是末尾。这导致频繁的"页分裂"(B-Tree 节点需要分裂以容纳新数据),随着数据量增大,写入性能显著下降。在高并发写入场景(如每秒数千次插入)中,随机 UUID 主键可能造成 2-5 倍的写入性能损失。读取性能方面,随机 UUID 会导致更多的页缓存失效(因为需要跳转到磁盘上的随机位置),增加 I/O 压力。
解决方案:有序 UUID
解决 UUID 主键性能问题的最佳方案是使用有序(时间有序)UUID:UUID v7 是最新的标准解决方案(RFC 9562,2024年),高 48 位是 Unix 毫秒时间戳,保证递增顺序;MySQL 的 UUID_TO_BIN(UUID(), 1) 函数将标准 UUID v1 的时间字段重排,使其成为有序的;SQL Server 的 NEWSEQUENTIALID() 生成顺序 GUID;第三方方案如 ULID(Universally Unique Lexicographically Sortable Identifier)也是很好的选择。有序 UUID 的写入性能接近自增整数 ID,同时保留了 UUID 的分布式友好性。
各数据库的 UUID 存储建议
- PostgreSQL:使用原生 UUID 类型(16 字节),性能最优;支持 gen_random_uuid()(v4)和 uuid_generate_v4() 函数
- MySQL 8.0+:使用 UUID_TO_BIN() 存为 BINARY(16),或使用 varchar(36) 兼容性最好;内置 UUID() 函数生成 v1,建议用 UUID_TO_BIN(UUID(), 1) 使其有序
- SQLite:没有 UUID 原生类型,存为 TEXT(36) 或 BLOB(16);UUID 必须在应用层生成
- SQL Server:使用 uniqueidentifier 类型(16 字节);优先使用 NEWSEQUENTIALID() 代替 NEWID() 获得更好的索引性能
- MongoDB:ObjectId 是内置的有序 ID(12 字节),通常比 UUID 更好;如需 UUID,使用 UUID BSON 子类型
MySQL 中的最佳实践代码
-- MySQL 8.0 中最优的 UUID 主键方案
CREATE TABLE orders (
id BINARY(16) NOT NULL DEFAULT (UUID_TO_BIN(UUID(), 1)),
customer_id INT NOT NULL,
total DECIMAL(10, 2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
-- 插入记录
INSERT INTO orders (customer_id, total) VALUES (1, 99.99);
-- 查询时转回字符串
SELECT BIN_TO_UUID(id, 1) as id, customer_id, total
FROM orders;
-- 按 UUID 字符串查询
SELECT * FROM orders
WHERE id = UUID_TO_BIN('550e8400-e29b-11d4-a716-446655440000', 1);
-- PostgreSQL 中的简洁方案
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
是否应该同时保留整数 ID
在某些场景中,同时保留 UUID 主键和自增整数 ID(作为内部 ID)是合理的:UUID 用作对外暴露的资源标识符(在 URL 和 API 响应中),整数 ID 用于内部外键引用(提高连表性能);不过这增加了存储和维护成本。另一种做法是使用整数主键,UUID 作为额外的 public_id 列(加唯一索引),仅在对外 API 中使用 UUID。这个方案兼顾了性能和安全性,但需要额外的数据库列和索引。具体选择取决于系统的规模、性能要求和安全需求。
立即免费使用相关工具
免费使用 →