目录1.存储空间问题存储大小对比2.索引性能问题(最核心问题)InnoDB 聚簇索引特性性能影响对比3.页分裂与碎片化页分裂过程4.缓存效率问题InnoDB Buffer Pool 工作原理5.具体性能测试对比测试数据6.实际场景分析适合使用UUID的场景不适合使用UUID的场景7.优化方案方案1:组合使用方案2:有序UUID方案3:雪花算法(Snowflake)8.MySQL 8.0 的改进9.监控指标10.决策指南何时可以使用UUID?应该避免使用UUID?总结1.存储空间问题存储大小对比主键类型存储
先来看看不同主键类型的存储开销对比。这个问题虽然基础,却是后续所有性能问题的根源。
| 主键类型 | 存储大小 | 示例值 |
|---|---|---|
| BIGINT(自增) | 8字节 | 1, 2, 3... |
| INT(自增) | 4字节 | 1, 2, 3... |
| UUID(字符串) | 36字符(288位) | uuid-xxxx-xxxx-xxxx |
| UUID(二进制) | 16字节 | 二进制格式 |
-- UUID 的两种存储方式
CREATE TABLE users_uuid_str (
id CHAR(36) PRIMARY KEY DEFAULT UUID(), -- 36字节
name VARCHAR(50)
);
CREATE TABLE users_uuid_bin (
id BINARY(16) PRIMARY KEY, -- 16字节,但仍然有其他问题
name VARCHAR(50)
);
这才是问题的关键所在。InnoDB的聚簇索引意味着数据本身就是按照主键顺序存储的,这个特性直接决定了两种主键的天地之别。
-- InnoDB 表结构示例 -- 数据实际按主键顺序存储在磁盘上 -- 自增ID:数据物理存储是连续的 -- UUID:数据物理存储是随机的
来看一个具体的插入场景对比,你就能明白为什么UUID在高并发下会成为性能杀手。
-- 场景:插入100万条数据
-- 使用自增ID
INSERT INTO table (name) VALUES ('name'); -- 直接追加到B+树末尾
-- 使用UUID
INSERT INTO table (id, name) VALUES (UUID(), 'name'); -- 需要:1. 在B+树中寻找插入位置 2. 可能导致页分裂 3. 碎片化
页分裂是随机插入带来的直接恶果。想象一下,原本整齐排列的数据页,因为一个随机UUID的插入而被硬生生撕裂。
原始页(已满):[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 新插入UUID:需要插入到 5 和 6 之间 结果: 页1:[1, 2, 3, 4, 5] 页2:[uuid_value, 6, 7, 8, 9, 10] 问题: 1. 数据不再连续 2. 磁盘空间利用率下降 3. 查询需要更多磁盘I/O
缓存效率的差异往往被忽视,但实际上它对系统整体性能的影响同样显著。
-- 自增ID:连续的数据更容易一起被缓存 -- 读取用户1-100的数据可能只需要1-2次磁盘I/O -- UUID:数据分散在不同页中 -- 读取100个用户数据可能需要100次磁盘I/O
理论说再多,不如实际测试来得有说服力。下面这个测试结果来自典型的线上环境配置。
-- 创建测试表
CREATE TABLE test_autoinc (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
data VARCHAR(100)
) ENGINE=InnoDB;
CREATE TABLE test_uuid (
id CHAR(36) PRIMARY KEY DEFAULT UUID(),
data VARCHAR(100)
) ENGINE=InnoDB;
-- 插入性能对比(100万行)
-- 自增ID:约 30-40秒
-- UUID:约 90-120秒(慢2-3倍)
-- 查询性能对比(范围查询)
SELECT * FROM test_autoinc WHERE id BETWEEN 100000 AND 200000;
-- 使用聚簇索引,高效
SELECT * FROM test_uuid WHERE id > 'xxxx';
-- 索引效率低,需要更多随机I/O
当然,UUID并非一无是处。在某些特定场景下,它的分布式特性确实无可替代。
-- 分布式系统,需要离线生成ID -- 数据需要合并的场景 -- 安全要求高,不希望暴露数据规模 -- 示例:移动设备离线数据同步
但话说回来,大多数业务场景其实并不需要UUID的那些"高级特性"。
-- 高并发写入的OLTP系统 -- 需要频繁范围查询的业务 -- 数据量大的表(>1000万行) -- 示例:电商订单、用户表、日志表
想要鱼与熊掌兼得?这个组合方案或许是最佳选择。
-- 使用自增ID作为主键,UUID作为业务ID
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- 用于索引和关联
uuid CHAR(36) UNIQUE NOT NULL DEFAULT UUID(), -- 对外暴露
name VARCHAR(50),
INDEX idx_uuid(uuid)
);
如果必须使用UUID,至少让它变得有序一些。
-- 使用时间有序的UUID变体
-- MySQL 8.0+ 的 UUID_TO_BIN 函数
CREATE TABLE users (
id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID(), 1)), -- 有序
name VARCHAR(50)
);
-- 参数1:将时间部分移到前面,提高顺序性
分布式ID生成的另一个选择,兼顾了性能和分布式特性。
# 分布式ID生成算法(64位) # 结构:时间戳(41位) + 机器ID(10位) + 序列号(12位) # 优点:有序、分布式、高性能
新版本确实带来了一些改进,但离完美还有距离。
-- 生成有序UUID SELECT UUID_TO_BIN(UUID(), 1); -- 有序 SELECT UUID_TO_BIN(UUID(), 0); -- 无序 -- 反向转换 SELECT BIN_TO_UUID(binary_uuid, 1);
用了UUID之后,这些监控指标必须重点关注。
-- 查看碎片化程度
SELECT
table_name,
data_length,
index_length,
data_free,
ROUND(data_free/(data_length+index_length)*100, 2) as frag_percent
FROM information_schema.tables
WHERE table_schema = DATABASE();
-- 监控插入性能
SHOW ENGINE INNODB STATUS;
说到底,在大多数OLTP场景中,自增整数主键依然是最稳妥的选择。UUID的主要问题在于破坏了InnoDB聚簇索引的顺序性,由此引发的一系列连锁反应——页分裂、碎片化、缓存效率低下,个个都是性能杀手。如果业务确实需要使用UUID,那么有序UUID或组合方案应该是优先考虑的方向,同时务必建立完善的监控机制来及时发现性能隐患。
到此这篇关于MySQL 主键不推荐使用 UUID 的深层原因的文章就介绍到这了,更多相关mysql主键不推荐使用uuid内容请搜索脚本大全以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本大全!
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述