当前位置: 首页 > 科技观察

使用uuid作为数据库的主键,被技术总监惊呆了!

时间:2023-03-23 09:52:15 科技观察

本文转载自微信公众号《Java极客技术》,作者鸭血范。转载本文请联系Java极客技术公众号。1.总结在日常开发中,数据库中生成主键id的方法主要有3种。数据库自增ID使用随机数生成不重复的ID。使用jdk提供的uuid。对于这三种方案,我发现在数据量较小的时候,并没有特别的区别,但是当单表的数据量达到百万级以上时,它们的性能就有明显的差异。光有理论是不够的,还得靠实际程序测试。今天,小编就带大家一探究竟!2、程序示例首先,我们在本地数据库中创建三张单表tb_uuid_1、tb_uuid_2、tb_uuid_3,并将tb_uuid_1表的主键设置为自增长模式。脚本如下:CREATETABLE`tb_uuid_1`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENT,`name`varchar(20)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ciCOMMENT='主键IDself-生长';CREATETABLE`tb_uuid_2`(`id`bigint(20)unsignedNOTNULL,`name`varchar(20)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ciCOMMENT='主键ID随机数生成';CREATETABLE`tb_uuid_3`(`id`varchar(50)NOTNULL,`name`varchar(20)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ciCOMMENT='主键由uuid生成';接下来我们使用Springboot+mybatis来实现插入测试。2.1.数据库自增以数据库自增为例,先写各种实体和数据持久层操作,方便后续测试/***表实体*/publicclassUUID1implementsSerializable{privateLongid;privateStringname;//省略set,get}/***数据持久层操作*/publicinterfaceUUID1Mapper{/***自增长插入*@paramuuid1*/@Insert("INSERTIINTOtb_uuid_1(name)VALUES(#{name})")voidinsert(UUID1uuid1);}/***Auto-增量ID,单元测试*/@TestpublicvoidtestInsert1(){longstart=System.currentTimeMillis();for(inti=0;i<1000000;i++){uuid1Mapper.insert(newUUID1().setName("张三"));}longend=System.currentTimeMillis();System.out.println("花费的时间:"+(end-start));}2.2。使用随机数生成ID这里我们使用twitter的雪花算法实现随机ID工具类如下:publicclassSnowflakeIdWorker{privatestaticSnowflakeIdWorkerinstance=newSnowflakeIdWorker(0,0);/***开始时间cutoff(2015-01-01)*/privatefinallongtwepoch=1420041600000L;/***机器id占用的位数*/privatefinallongworkerIdBits=5L;/***数据标识符id占用的位数*/privatefinallongdatacenterIdBits=5L;/***最大支持的机器id,结果为31(这个移位算法可以快速算出几个二进制数所能表示的最大十进制数)*/privatefinallongmaxWorkerId=-1L^(-1L<maxWorkerId||workerId<0){thrownewIllegalArgumentException(String.format("workerIdcan'tbegreaterthan%dorlessthan0",maxWorkerId));}if(datacenterId>maxDatacenterId||datacenterId<0){thrownewIllegalArgumentException(String.format("datacenterIdcan'tbegreaterthan%dorlessthan0",maxDatacenterId));}this.workerId=workerId;this.datacenterId=datacenterId;}/***获取下一个ID(the该方法是线程安全的)*@returnSnowflakeId*/publicsynchronizedlongnextId(){longtimestamp=timeGen();//如果当前时间小于上次生成ID的时间戳,说明系统时钟回滚应该抛异常if(timestamp雪花算法生成的ID>>uuid生成的ID在数据量很大的情况下,为什么uuid生成的ID远不如自??增呢?ID?关于这一点,我们可以从mysql主键存储的内部结构来分析。3.1.自增ID的内部结构自增主键的值是有顺序的,所以Innodb将每条记录存储在一条记录后面。当达到页面的最大填充因子时(innodb默认的最大填充因子是页面大小的15/16,1/16的空间会预留给以后修改),会进行如下操作:record会写在新的page中,一旦按照这个顺序加载数据,主键page就会被几乎连续的record填充,这样就增加了page的最大填充率,不会有pages的浪费。新插入的行肯定会在原来最大数据行的下一行,mysql定位寻址非常快,不会为计算新行的位置做额外的消耗也就是说新行的值为不一定大于前一个主键的值,所以innodb不能总是在索引的末尾插入新行,而是需要为新行找到一个新的合适的位置来分配新的空间。这个过程需要很多额外的操作,而且数据的无序会导致数据分布分散,会导致以下问题:没有被加载到缓存中,innodb在插入之前必须从磁盘中找到并读取目标页面到内存中,这会造成大量的随机IO。因为写乱序,innodb不得不经常做pagesplitting,这样才能为新的行分配空间,pagesplitting导致大量的数据移动,一次插入至少需要修改三个page。由于频繁的分页,页面会变得稀疏,填充不规律,最终导致数据碎片化。将值加载到聚簇索引(innodb默认的索引类型)后,有时需要做一次OPTIMEIZETABLE重建表和优化页面的填充,这会花费一定的时间。因此,在选择主键ID生成方案时,尽量不要使用uuid来生成主键ID。随着数据量的增加,插入性能会降低!4.总结在实际使用中,推荐使用主键自增ID和雪花算法生成的随机ID。但是使用自增ID也有缺点:1、一旦有人爬取你的数据库,就可以根据数据库的自增ID获取你的业务增长信息,很容易窃取数据。2、其次,对于高并发负载,InnoDB在按照主键插入时会造成明显的锁竞争,主键的上界会成为竞争的热点,因为所有的插入都发生在这里,并发插入会造成间隙锁争用。综上所述,如果业务量小,建议使用自增ID,如果业务量大,建议使用雪花算法生成的随机ID。本文主要从实际程序实例出发,探讨三种主键ID生成方案的性能差异。鉴于笔者知识的不足,可能有些地方理解不到位。欢迎广大网友批评指正!

最新推荐
猜你喜欢