广告
返回顶部
首页 > 资讯 > 数据库 >为什么不能用uuid作为数据库主键
  • 132
分享到

为什么不能用uuid作为数据库主键

2024-04-02 19:04:59 132人浏览 八月长安
摘要

这篇文章主要介绍“为什么不能用uuid作为数据库主键”,在日常操作中,相信很多人在为什么不能用uuid作为数据库主键问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”为什么不能用

这篇文章主要介绍“为什么不能用uuid作为数据库主键”,在日常操作中,相信很多人在为什么不能用uuid作为数据库主键问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”为什么不能用uuid作为数据库主键”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

 一、摘要

在日常开发中,数据库中主键id的生成方案,主要有三种

数据库自增ID

采用随机数生成不重复的ID

采用jdk提供的uuid

对于这三种方案,我发现在数据量少的情况下,没有特别的差异,但是当单表的数据量达到百万级以上时候,他们的性能有着显著的区别,光说理论不行,还得看实际程序测试,今天小编就带着大家一探究竟!

二、程序实例

首先,我们在本地数据库中创建三张单表tb_uuid_1、tb_uuid_2、tb_uuid_3,同时设置tb_uuid_1表的主键为自增长模式,脚本如下:

CREATE TABLE `tb_uuid_1` (   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,   `name` varchar(20) DEFAULT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主键ID自增长';
CREATE TABLE `tb_uuid_2` (   `id` bigint(20) unsigned NOT NULL,   `name` varchar(20) DEFAULT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主键ID随机数生成';
CREATE TABLE `tb_uuid_3` (   `id` varchar(50)  NOT NULL,   `name` varchar(20) DEFAULT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主键采用uuid生成';

下面,我们采用SpringBoot + mybatis来实现插入测试。

2.1、数据库自增

以数据库自增为例,首先编写好各种实体、数据持久层操作,方便后续进行测试

 public class UUID1 implements Serializable {      private Long id;      private String name;      //省略set、get }
 public interface UUID1Mapper {           @Insert("INSERT INTO tb_uuid_1(name) VALUES(#{name})")     void insert(UUID1 uuid1); }
 @Test public void testInsert1(){     long start = System.currentTimeMillis();     for (int i = 0; i < 1000000; i++) {         uuid1Mapper.insert(new UUID1().setName("张三"));     }     long end = System.currentTimeMillis();     System.out.println("花费时间:" +  (end - start)); }

2.2、采用随机数生成ID

这里,我们采用twitter的雪花算法来实现随机数ID的生成,工具类如下:

public class SnowflakeIdWorker {      private static SnowflakeIdWorker instance = new SnowflakeIdWorker(0,0);           private final long twepoch = 1420041600000L;          private final long workerIdBits = 5L;          private final long datacenterIdBits = 5L;          private final long maxWorkerId = -1L ^ (-1L << workerIdBits);          private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);          private final long sequenceBits = 12L;          private final long workerIdShift = sequenceBits;          private final long datacenterIdShift = sequenceBits + workerIdBits;          private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;          private final long sequenceMask = -1L ^ (-1L << sequenceBits);          private long workerId;          private long datacenterId;          private long sequence = 0L;          private long lastTimestamp = -1L;          public SnowflakeIdWorker(long workerId, long datacenterId) {         if (workerId > maxWorkerId || workerId < 0) {             throw new IllegalArgumentException(String.fORMat("worker Id can't be greater than %d or less than 0", maxWorkerId));         }         if (datacenterId > maxDatacenterId || datacenterId < 0) {             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));         }         this.workerId = workerId;         this.datacenterId = datacenterId;     }          public synchronized long nextId() {         long timestamp = timeGen();         // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常         if (timestamp < lastTimestamp) {             throw new RuntimeException(                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));         }         // 如果是同一时间生成的,则进行毫秒内序列         if (lastTimestamp == timestamp) {             sequence = (sequence + 1) & sequenceMask;             // 毫秒内序列溢出             if (sequence == 0) {                 //阻塞到下一个毫秒,获得新的时间戳                 timestamp = tilNextMillis(lastTimestamp);             }         }         // 时间戳改变,毫秒内序列重置         else {             sequence = 0L;         }         // 上次生成ID的时间截         lastTimestamp = timestamp;         // 移位并通过或运算拼到一起组成64位的ID         return ((timestamp - twepoch) << timestampLeftShift) //                 | (datacenterId << datacenterIdShift) //                 | (workerId << workerIdShift) //                 | sequence;     }          protected long tilNextMillis(long lastTimestamp) {         long timestamp = timeGen();         while (timestamp <= lastTimestamp) {             timestamp = timeGen();         }         return timestamp;     }          protected long timeGen() {         return System.currentTimeMillis();     }      public static SnowflakeIdWorker getInstance(){         return instance;     }       public static void main(String[] args) throws InterruptedException {         SnowflakeIdWorker idWorker = SnowflakeIdWorker.getInstance();         for (int i = 0; i < 10; i++) {             long id = idWorker.nextId();             Thread.sleep(1);             System.out.println(id);         }     } }

其他的操作,与上面类似。

2.3、uuid

同样的,uuid的生成,我们事先也可以将工具类编写好:

public class UUIDGenerator {           public static String getUUID(){         return UUID.randomUUID().toString();     } }

最后的单元测试,代码如下:

@RunWith(springRunner.class) @SpringBootTest() public class UUID1Test {      private static final Integer MAX_COUNT = 1000000;      @Autowired     private UUID1Mapper uuid1Mapper;      @Autowired     private UUID2Mapper uuid2Mapper;      @Autowired     private UUID3Mapper uuid3Mapper;           @Test     public void testInsert1(){         long start = System.currentTimeMillis();         for (int i = 0; i < MAX_COUNT; i++) {             uuid1Mapper.insert(new UUID1().setName("张三"));         }         long end = System.currentTimeMillis();         System.out.println("自增ID,花费时间:" +  (end - start));     }           @Test     public void testInsert2(){         long start = System.currentTimeMillis();         for (int i = 0; i < MAX_COUNT; i++) {             long id = SnowflakeIdWorker.getInstance().nextId();             uuid2Mapper.insert(new UUID2().setId(id).setName("张三"));         }         long end = System.currentTimeMillis();         System.out.println("花费时间:" +  (end - start));     }           @Test     public void testInsert3(){         long start = System.currentTimeMillis();         for (int i = 0; i < MAX_COUNT; i++) {             String id = UUIDGenerator.getUUID();             uuid3Mapper.insert(new UUID3().setId(id).setName("张三"));         }         long end = System.currentTimeMillis();         System.out.println("花费时间:" +  (end - start));     } }

三、性能测试

程序环境搭建完成之后,啥也不说了,直接撸起袖子,将单元测试跑起来!

首先测试一下,插入100万数据的情况下,三者直接的耗时结果如下:

为什么不能用uuid作为数据库主键

为什么不能用uuid作为数据库主键

为什么不能用uuid作为数据库主键

为什么不能用uuid作为数据库主键

在原有的数据量上,我们继续插入30万条数据,三者耗时结果如下:

为什么不能用uuid作为数据库主键

可以看出在数据量 100W 左右的时候,uuid的插入效率垫底,随着插入的数据量增长,uuid 生成的ID插入呈直线下降!

时间占用量总体效率排名为:自增ID > 雪花算法生成的ID >> uuid生成的ID。

在数据量较大的情况下,为什么uuid生成的ID远不如自增ID呢?

关于这点,我们可以从 Mysql 主键存储的内部结构来进行分析。

3.1、自增ID内部结构

自增的主键的值是顺序的,所以 Innodb 把每一条记录都存储在一条记录的后面。

为什么不能用uuid作为数据库主键

当达到页面的最大填充因子时候(innodb默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的修改),会进行如下操作:

  • 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费

  • 新插入的行一定会在原有的最大数据行下一行,mysql定位和寻址很快,不会为计算新行的位置而做出额外的消耗

3.2、使用uuid的索引内部结构

uuid相对顺序的自增id来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以innodb无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。

为什么不能用uuid作为数据库主键

这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:

  • 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机IO

  • 因为写入是乱序的,innodb不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上

  • 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片

在把值载入到聚簇索引(innodb默认的索引类型)以后,有时候会需要做一次OPTIMEIZE  TABLE来重建表并优化页的填充,这将又需要一定的时间消耗。

因此,在选择主键ID生成方案的时候,尽可能别采用uuid的方式来生成主键ID,随着数据量越大,插入性能会越低!

四、总结

在实际使用过程中,推荐使用主键自增ID和雪花算法生成的随机ID。

但是使用自增ID也有缺点:

1、别人一旦爬取你的数据库,就可以根据数据库的自增id获取到你的业务增长信息,很容易进行数据窃取。2、其次,对于高并发的负载,innodb在按主键进行插入的时候会造成明显的争用,主键的上界会成为争抢的热点,因为所有的插入都发生在这里,并发插入会导致间隙锁竞争。

总结起来,如果业务量小,推荐采用自增ID,如果业务量大,推荐采用雪花算法生成的随机ID。

到此,关于“为什么不能用uuid作为数据库主键”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

您可能感兴趣的文档:

--结束END--

本文标题: 为什么不能用uuid作为数据库主键

本文链接: https://www.lsjlt.com/news/56251.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 为什么不能用uuid作为数据库主键
    这篇文章主要介绍“为什么不能用uuid作为数据库主键”,在日常操作中,相信很多人在为什么不能用uuid作为数据库主键问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”为什么不能用...
    99+
    2022-10-18
  • MySQL为什么不能用uuid做主键
    今天小编给大家分享一下MySQL为什么不能用uuid做主键的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起...
    99+
    2022-10-19
  • MySQL为什么不建议用UUID做innodb主键
    这篇文章将为大家详细讲解有关MySQL为什么不建议用UUID做innodb主键,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。 1).UUID...
    99+
    2022-10-18
  • 深入分析mysql为什么不推荐使用uuid或者雪花id作为主键
    前言:在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,...
    99+
    2022-05-25
    mysql uuid 主键 mysql 雪花id主键
  • 为什么数据库不应该使用外键
    为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面...
    99+
    2019-08-06
    为什么数据库不应该使用外键 数据库入门 数据库基础教程 数据库 mysql
  • mysql innodb为什么建议使用自增数字作为主键?
    1.建议使用有序的自增ID作为主键 MySQL InnoDB 引擎底层数据结构是 B+ 树,所谓的索引其实就是一颗 B+ 树,一个表有多少个索引就会有多少颗 B+ 树,mysql 中的数据都是按顺序保存在 B+ 树叶子节点上的(所以说索引...
    99+
    2019-03-20
    mysql innodb为什么建议使用自增数字作为主键? 数据库入门 数据库基础教程 数据库 mysql
  • 为什么不使用MySQL数据库
    小编给大家分享一下为什么不使用MySQL数据库,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  1、MySQL(和PHP搭配之最...
    99+
    2022-10-19
  • 在vue中为什么不能用index作为key
    目录一、key的作用是什么?二、什么是虚拟DOM?三、什么是diff算法四、为什么不能用index作为key?(1)index不能作为key--情景一(2)index不能作为key-...
    99+
    2022-11-13
  • 数据库,主键为何不宜太长长长长长长长长?
    回答星球水友提问:沈老师,我听网上说,MySQL数据表,在数据量比较大的情况下,主键不宜过长,是不是这样呢?这又是为什么呢? 这个问题嘛,不能一概而论:(1)如果是InnoDB存储引擎,主键不宜过...
    99+
    2022-10-18
  • 数据库中BDE Administrator为什么不可用
    BDE Administrator不可用的可能原因有以下几点: 未正确安装BDE组件:BDE Administrator是Bor...
    99+
    2023-10-27
    数据库
  • 云服务器包含数据库吗为什么不能用
    云服务器(Cloud Storage)是一种基于互联网的服务器,它可以通过互联网访问。但是,由于互联网的安全性和隐私保护等因素,大量的用户数据存储在本地服务器上可能会对服务器的性能或可靠性产生负面影响。因此,许多企业和组织倾向于将他们的数据...
    99+
    2023-10-26
    不能用 服务器 数据库
  • 云服务器包括数据库吗为什么不能用
    云服务器(Cloud Server)可以被称为一种虚拟服务器,它可以通过互联网连接到用户的主机上。这意味着在云服务器上运行的应用程序可以托管在云服务器上的虚拟机中,并且不需要连接到专门的数据中心。 然而,使用云服务器也存在一些限制。首先,...
    99+
    2023-10-26
    不能用 服务器 数据库
  • 数据库中为什么我们需要使用键
    这篇文章将为大家详细讲解有关数据库中为什么我们需要使用键,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。为什么我们需要使用键?在实际应用中,存储数据所需的表的数量很大,并且...
    99+
    2022-10-18
  • 为什么dsa不能用于数据加密
    dsa是一个非对称加密算法,其安全性是基于整数有限域离散对数难题;dsa一般用于数字签名和认证,在dsa数字签名和认证中,发送者使用自己的私钥对文件或消息进行签名,接受者收到消息后使用发送者的公钥来验证签名的真实性。dsa只是一种算法,所以...
    99+
    2022-10-12
  • MySQL数据库自增主键的间隔不为1的解决方式
    目录mysql数据库自增主键的间隔不为1fvt测试环境数据库配置pr类生产环境数据库配置SQL语句中的auto_increment=1是什么意思?举个简单例子MySQL数据库自增主键的间隔不为1 今天新版本上线的时候,遇...
    99+
    2022-12-27
    MySQL数据库 MySQL自增主键 MySQL自增主键不为1
  • 云服务器包含数据库吗为什么不能用了
    云服务器包含数据库是正常情况下的,因为云服务器通常是由大型数据中心托管的,因此可能会包含一些存储数据的实例。这些数据可以存储到云服务器的数据库中,或者可以存储在多个云服务器之间的备份存储中。 此外,有时候,用户需要将数据保存在云服务器的特...
    99+
    2023-10-27
    用了 服务器 数据库
  • 云服务器包括数据库吗为什么不能用了
    云服务器通常指的是云平台服务或者云存储服务,它们通常是由公共云服务提供商提供的。因此,您并不需要购买特定的云服务器软件,或者使用专门的云服务器软件来存储您的数据。 如果您需要使用云服务器,则需要了解具体情况。例如,如果您使用的是云计算服务...
    99+
    2023-10-26
    用了 服务器 数据库
  • 建Mysql数据库时为什么不适用utf8
    本篇文章给大家主要讲的是关于建Mysql数据库时为什么不适用utf8的内容,感兴趣的话就一起来看看这篇文章吧,相信看完建Mysql数据库时为什么不适用utf8对大家多少有点参考价值吧。 当然,现在...
    99+
    2022-10-18
  • 【MySQL】mysql中不推荐使用uuid或者雪花id作为主键的原因以及差异化对比
    文章目录 前言什么是UUID什么是雪花ID什么是MySql自增ID优缺点对比UUID:优点1.全球唯一性2.无需数据库支持 缺点1.存储空间大2.索引效率低3.查询效率低 雪花ID:优点1.分布式环...
    99+
    2023-12-22
    mysql 数据库
  • mysql互为主从的环境为什么会出现数据不一致
    本篇内容介绍了“mysql互为主从的环境为什么会出现数据不一致”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成...
    99+
    2022-10-18
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作