MySQL 主键不推荐使用 UUID 的深层原因及解决方案
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">1.存储空间问题</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">存储大小对比</a></li></ul><li><a href="#_label1">2.索引性能问题(最核心问题)</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_1">InnoDB 聚簇索引特性</a></li><li><a href="#_lab2_1_2">性能影响对比</a></li></ul><li><a href="#_label2">3.页分裂与碎片化</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_3">页分裂过程</a></li></ul><li><a href="#_label3">4.缓存效率问题</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_4">InnoDB Buffer Pool 工作原理</a></li></ul><li><a href="#_label4">5.具体性能测试对比</a></li><ul class="second_class_ul"><li><a href="#_lab2_4_5">测试数据</a></li></ul><li><a href="#_label5">6.实际场景分析</a></li><ul class="second_class_ul"><li><a href="#_lab2_5_6">适合使用UUID的场景</a></li><li><a href="#_lab2_5_7">不适合使用UUID的场景</a></li></ul><li><a href="#_label6">7.优化方案</a></li><ul class="second_class_ul"><li><a href="#_lab2_6_8">方案1:组合使用</a></li><li><a href="#_lab2_6_9">方案2:有序UUID</a></li><li><a href="#_lab2_6_10">方案3:雪花算法(Snowflake)</a></li></ul><li><a href="#_label7">8.MySQL 8.0 的改进</a></li><ul class="second_class_ul"></ul><li><a href="#_label8">9.监控指标</a></li><ul class="second_class_ul"></ul><li><a href="#_label9">10.决策指南</a></li><ul class="second_class_ul"><li><a href="#_lab2_9_11">何时可以使用UUID?</a></li><li><a href="#_lab2_9_12">应该避免使用UUID?</a></li></ul><li><a href="#_label10">总结</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>1.存储空间问题</h2><p class="maodian"><a name="_lab2_0_0"></a></p><h3>存储大小对比</h3>
<table><thead><tr><th>主键类型</th><th>存储大小</th><th>示例值</th></tr></thead><tbody><tr><td>BIGINT(自增)</td><td>8字节</td><td>1, 2, 3...</td></tr><tr><td>INT(自增)</td><td>4字节</td><td>1, 2, 3...</td></tr><tr><td>UUID(字符串)</td><td>36字符(288位)</td><td><code>uuid-xxxx-xxxx-xxxx</code></td></tr><tr><td>UUID(二进制)</td><td>16字节</td><td>二进制格式</td></tr></tbody></table>
<div class="jb51code"><pre class="brush:sql;">-- 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)
);</pre></div>
<p class="maodian"><a name="_label1"></a></p><h2>2.索引性能问题(最核心问题)</h2>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>InnoDB 聚簇索引特性</h3>
<div class="jb51code"><pre class="brush:sql;">-- InnoDB 表结构示例
-- 数据实际按主键顺序存储在磁盘上
-- 自增ID:数据物理存储是连续的
-- UUID:数据物理存储是随机的</pre></div>
<p class="maodian"><a name="_lab2_1_2"></a></p><h3>性能影响对比</h3>
<div class="jb51code"><pre class="brush:sql;">-- 场景:插入100万条数据
-- 使用自增ID
INSERT INTO table (name) VALUES ('name');-- 直接追加到B+树末尾
-- 使用UUID
INSERT INTO table (id, name) VALUES (UUID(), 'name');
-- 需要:1. 在B+树中寻找插入位置 2. 可能导致页分裂 3. 碎片化</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>3.页分裂与碎片化</h2>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>页分裂过程</h3>
<div class="jb51code"><pre class="brush:sql;">原始页(已满):
新插入UUID:需要插入到 5 和 6 之间
结果:
页1:
页2:
问题:
1. 数据不再连续
2. 磁盘空间利用率下降
3. 查询需要更多磁盘I/O</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>4.缓存效率问题</h2>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>InnoDB Buffer Pool 工作原理</h3>
<div class="jb51code"><pre class="brush:sql;">-- 自增ID:连续的数据更容易一起被缓存
-- 读取用户1-100的数据可能只需要1-2次磁盘I/O
-- UUID:数据分散在不同页中
-- 读取100个用户数据可能需要100次磁盘I/O</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>5.具体性能测试对比</h2>
<p class="maodian"><a name="_lab2_4_5"></a></p><h3>测试数据</h3>
<div class="jb51code"><pre class="brush:sql;">-- 创建测试表
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</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>6.实际场景分析</h2>
<p class="maodian"><a name="_lab2_5_6"></a></p><h3>适合使用UUID的场景</h3>
<div class="jb51code"><pre class="brush:sql;">-- 分布式系统,需要离线生成ID
-- 数据需要合并的场景
-- 安全要求高,不希望暴露数据规模
-- 示例:移动设备离线数据同步</pre></div>
<p class="maodian"><a name="_lab2_5_7"></a></p><h3>不适合使用UUID的场景</h3>
<div class="jb51code"><pre class="brush:sql;">-- 高并发写入的OLTP系统
-- 需要频繁范围查询的业务
-- 数据量大的表(>1000万行)
-- 示例:电商订单、用户表、日志表</pre></div>
<p class="maodian"><a name="_label6"></a></p><h2>7.优化方案</h2>
<p class="maodian"><a name="_lab2_6_8"></a></p><h3>方案1:组合使用</h3>
<div class="jb51code"><pre class="brush:sql;">-- 使用自增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)
);</pre></div>
<p class="maodian"><a name="_lab2_6_9"></a></p><h3>方案2:有序UUID</h3>
<div class="jb51code"><pre class="brush:sql;">-- 使用时间有序的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:将时间部分移到前面,提高顺序性</pre></div>
<p class="maodian"><a name="_lab2_6_10"></a></p><h3>方案3:雪花算法(Snowflake)</h3>
<div class="jb51code"><pre class="brush:sql;"># 分布式ID生成算法(64位)
# 结构:时间戳(41位) + 机器ID(10位) + 序列号(12位)
# 优点:有序、分布式、高性能</pre></div>
<p class="maodian"><a name="_label7"></a></p><h2>8.MySQL 8.0 的改进</h2>
<div class="jb51code"><pre class="brush:sql;">-- 生成有序UUID
SELECT UUID_TO_BIN(UUID(), 1);-- 有序
SELECT UUID_TO_BIN(UUID(), 0);-- 无序
-- 反向转换
SELECT BIN_TO_UUID(binary_uuid, 1);</pre></div>
<p class="maodian"><a name="_label8"></a></p><h2>9.监控指标</h2>
<div class="jb51code"><pre class="brush:sql;">-- 查看碎片化程度
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;</pre></div>
<p class="maodian"><a name="_label9"></a></p><h2>10.决策指南</h2>
<p class="maodian"><a name="_lab2_9_11"></a></p><h3>何时可以使用UUID?</h3>
<ul><li>✅ 数据量小(<100万行)</li><li>✅ 插入频率低</li><li>✅ 分布式系统必须使用</li><li>✅ 数据合并需求</li><li>✅ 安全要求高</li></ul>
<p class="maodian"><a name="_lab2_9_12"></a></p><h3>应该避免使用UUID?</h3>
<ul><li>❌ 高并发写入系统</li><li>❌ 大数据量表</li><li>❌ 频繁范围查询</li><li>❌ 性能敏感系统</li><li>❌ 磁盘空间有限</li></ul>
<p class="maodian"><a name="_label10"></a></p><h2>总结</h2>
<p><strong> 在大多数OLTP场景中,自增整数主键是最优选择</strong>。UUID主要问题是破坏InnoDB聚簇索引的顺序性,导致页分裂、碎片化、缓存效率低下等问题。如果必须使用UUID,应优先考虑有序UUID或组合方案,并监控性能影响。</p>
頁:
[1]