找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

575

积分

0

好友

76

主题
发表于 16 小时前 | 查看: 1| 回复: 0

在MySQL中设计表时,官方推荐使用连续自增的主键ID(AUTO_INCREMENT),而不是UUID或不连续不重复的雪花ID。为什么不建议采用UUID等随机值作为主键?使用它们究竟会带来哪些性能问题?本文将深入探讨这一问题。

一、实验准备与测试

为了直观地对比不同主键策略的差异,我们建立三张结构相同、仅主键生成策略不同的表:

  1. user_auto_key:使用自增ID (AUTO_INCREMENT)。
  2. user_uuid:使用UUID作为主键。
  3. user_random_key:使用雪花算法生成的随机Long型ID(一串18位不连续的数字)。

表结构示例:

  • id自增表:id bigint(20) NOT NULL AUTO_INCREMENT
  • 用户uuid表:id varchar(32) NOT NULL
  • 随机主键表:id bigint(20) NOT NULL

我们使用 SpringBoot + JdbcTemplate 编写测试程序,在相同环境下,向每张表插入同等数量的随机数据,并记录插入耗时。

核心测试代码片段:

@Test
void testDBTime() {
    StopWatch stopwatch = new StopWatch("执行sql时间消耗");

    // 测试自增ID表插入
    final String insertSql = "INSERT INTO user_key_auto(user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?)";
    List<UserKeyAuto> insertData = autoKeyTableService.getInsertData();
    stopwatch.start("自动生成key表任务开始");
    long start1 = System.currentTimeMillis();
    if (CollectionUtil.isNotEmpty(insertData)) {
        boolean insertResult = jdbcTemplateService.insert(insertSql, insertData, false);
    }
    long end1 = System.currentTimeMillis();
    System.out.println("auto key消耗的时间:" + (end1 - start1));
    stopwatch.stop();

    // 测试UUID表插入(代码类似,略)
    // 测试随机ID表插入(代码类似,略)

    System.out.println(stopwatch.prettyPrint());
}

二、测试结果分析

初始数据量130万时,插入10万数据的性能对比:

主键类型 耗时(近似)
自增ID (AUTO_INCREMENT) 约 7.5 秒
随机Long ID (雪花ID) 约 21 秒
UUID 约 39 秒

结论: 在大数据量插入场景下,性能排名为:自增ID > 随机ID(雪花ID)> UUID。UUID的插入效率最低,且随着数据量增长,其性能下降更为明显。

三、性能差异的底层原理

为何不同的主键策略会导致如此巨大的性能差距?这需要从 InnoDB索引的物理结构 说起。

1. 使用自增ID的索引结构

自增ID的值是连续递增的。
自增ID插入示意图

  • 顺序写入:新插入的数据行总是位于索引B+树的最右叶节点,即当前最大ID的后面。这种操作近乎顺序I/O,效率极高。
  • 高页填充率:数据按顺序填满页,能充分利用存储空间(默认填充因子15/16),减少页碎片。
  • 避免页分裂:由于总是追加写入,极大降低了因插入中间值而导致索引页分裂的概率。
2. 使用UUID/雪花ID的索引结构

UUID或雪花ID的值是无序的。
UUID插入示意图

  • 随机写入:新行的主键值无法保证比之前的大,InnoDB需要为它寻找合适的插入位置(可能在已有的数据页中间)。
  • 大量页分裂与碎片:为了给新数据腾出空间,InnoDB不得不频繁进行页分裂。这会导致:
    1. 随机I/O增加:目标数据页可能不在内存中,需要先从磁盘读取。
    2. 数据移动开销:页分裂需要移动大量已有数据。
    3. 空间碎片化:频繁分裂使数据页填充不规则,产生碎片,降低空间利用率和后续查询性能。
      这正是UUID插入性能远低于自增ID的根本原因。关于数据库索引的深入优化,可以参考云栈社区的数据库/中间件板块,里面有更多关于MySQL性能调优的实战内容。
3. 自增ID的潜在缺点

当然,自增ID也并非完美,存在一些需要注意的点:

  • 信息可推测性:自增ID可能暴露业务数据量或增长趋势。
  • 高并发插入热点:所有插入都集中在索引最右端,在极高并发场景下可能形成锁争用。
  • 自增锁开销AUTO_INCREMENT的锁机制(可通过innodb_autoinc_lock_mode参数调优)在特定场景下会带来一定的性能损耗。在实际开发中,合理使用例如SpringBoot等框架的事务管理,有助于理解和优化这类并发控制问题。

四、总结与选型建议

通过实验测试和原理分析,我们可以清晰地看到:

  • 性能上:自增ID在写入性能上具有压倒性优势,尤其适合写入密集型的应用。
  • 适用场景上
    • 自增ID:是绝大多数场景下的默认推荐选择,简单高效。
    • UUID / 雪花ID:适用于需要全局唯一分布式生成、或不希望暴露业务顺序的场景。但必须意识到其带来的写入性能损耗,并做好数据碎片整理(如定期OPTIMIZE TABLE)的准备。

MySQL的设计博大精深,深入理解其存储引擎的工作原理,对于进行高效的数据库设计至关重要。在选择主键策略时,应综合考虑数据量、并发度、分布式需求以及安全性等因素,没有绝对的最优解,只有最适合当前场景的平衡之选。




上一篇:ZooKeeper故障转移核心机制与节点过多性能优化实战
下一篇:Active Directory站点攻击分析:利用GPO链接进行权限提升与横向移动
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-10 20:28 , Processed in 0.077953 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表