前言:在mysql设计表的时候,mysql官方建议不要使用uuid或者不连续不重复的雪花id(长而唯一),推荐使用连续自增的主键id。官方推荐的是auto_increment,那么为什么不推荐使用uuid,使用uuid有什么缺点呢?在这篇博客中,我们将分析这个问题并讨论内部原因。一:mysql及程序示例1.1:为了说明这个问题,我们先创建三张表,分别是user_auto_key,user_uuid,user_random_key,分别代表自增长主键,uuid为主键,randomkey为主键,其他我们保持完全不变Change。根据控制变量的方法,我们只是使用不同的策略生成每个表的主键,其他字段完全一样,然后测试表的插入速度和查询速度:注:这里的随机键实际上指的是使用雪花算法计算出的id是不连续的,不重复的,不规则的:一串18位长值id自动生成表:useruuidtablerandomprimarykeytable:1.2:不够有理论就去程序用spring的jdbcTemplate实现附加check测试:技术框架:springboot+jdbcTemplate+junit+hutool,程序的原理是连接自己的测试数据库,然后写入等量同一环境下的数据,分析insert插入时间综合效率,为了达到最真实的效果,所有数据都是随机生成的,比如姓名,email地址,地址都是随机生成的,程序已经从gitee上传,地址在文末。包com.wyq.mysqldemo;导入cn.hutool.core.collection.CollectionUtil;导入com.wyq.mysqldemo.databaseobject.UserKeyAuto;导入com.wyq.mysqldemo.databaseobject.UserKeyRandom;导入com.wyq.mysqldemo.databaseobject.UserKeyUUID;导入com.wyq.mysqldemo.diffkeytest.AutoKeyTableService;导入com.wyq.mysqldemo.diffkeytest.RandomKeyTableService;导入com.wyq.mysqldemo.diffkeytest.UUIDKeyTableService;导入com.wyq.mysqldemo.util.JdbcTemplateService;导入org.junit。jupiter.api.Test;导入org.springframework.beans.factory.annotation.Autowired;导入org.springframework.boot.test.context.SpringBootTest;导入org.springframework.util.StopWatch;导入java.util.List;@SpringBootTestclassMysqlDemoApplicationTests{@AutowiredprivateJdbcTemplateServicejdbcTemplateService;@AutowiredprivateAutoKeyTableServiceautoKeyTableService;@AutowiredprivateUUIDKeyTableServiceuuidKeyTableService;@Autowired私人RandomKeyTableServicerandomKeyTableService;@TestvoidtestDBTime(){StopWatchstopwatch=newStopWatch("执行sql耗时");/***auto_incrementkeytask*/finalStringinsertSql="INSERTINTOuser_key_auto(user_id,user_name,sex,address,city,email,state)VALUES(?,?,?,?,?,?,?)";ListinsertData=autoKeyTableService.getInsertData();stopwatch.start("自动生成键表任务开始");longstart1=System.currentTimeMillis();如果(CollectionUtil.isNotEmpty(insertData)){布尔insertResult=jdbcTemplateService.insert(insertSql,insertData,false);System.out.println(insertResult);}longend1=System.currentTimeMillis();System.out.println("自动密钥消耗时间:"+(end1-start1));秒表.stop();/***uudIDkey*/finalStringinsertSql2="INSERTINTOuser_uuid(id,user_id,user_name,sex,address,city,email,state)VALUES(?,?,?,?,?,?,?,?)";ListinsertData2=uuidKeyTableService.getInsertData();stopwatch.start("UUID键表任务开始");longbegin=System.currentTimeMillis();if(CollectionUtil.isNotEmpty(insertData)){booleaninsertResult=jdbcTemplateService.insert(insertSql2,insertData2,true);System.out.println(insertResult);}longover=System.currentTimeMillis();System.out.println("UUID密钥消耗时间:"+(over-begin));stopwatch.stop();/***随机长值密钥*/finalStringinsertSql3="INSERTINTOuser_random_key(id,user_id,user_name,sex,address,city,email,state)VALUES(?,?,?,?,?,?,?,?)";ListinsertData3=randomKeyTableService.getInsertData();stopwatch.start("随机长值键表任务开始");Longstart=System.currentTimeMillis();如果(CollectionUtil.isNotEmpty(insertData)){booleaninsertResult=jdbcTemplateService.insert(insertSql3,insertData3,true);System.out.println(insertResult);}长端=System.currentTimeMillis();System.out.println("随机key任务消耗时间:"+(end-start));秒表.stop();字符串结果=stopwatch.prettyPrint();System.out.println(结果);}1.3:程序写入结果User_key_auto写入结果:user_random_key写入结果:user_uuid表写入结果:1.4:效率测试结果现有数据量为130W时:再测试插入10w数据,看看结果如何:可以看到也就是说在100W左右数据量的时候,uuid的插入效率垫底,后面的序列又增加了130W的数据,uudi的时间直线下降,在数据量很大的情况下,效率直线下降。那么为什么会出现这样的现象呢?带着疑惑,来讨论一下这个问题:2:使用uuid和自增id的索引结构比较2.1:使用自增id的内部结构自增主键的值是顺序的,所以Innodb存储每条记录后一个记录。当达到页面的最大填充因子时(innodb默认的最大填充因子是页面大小的15/16,会预留1/16的空间用于以后的修改):①下一条记录会被写入一个新的page,一旦按照这个顺序加载数据,主键页将几乎顺序填充记录,增加页的最大填充率,不会有页浪费②新插入的行必须在原来的最大值datarow对于下一行,mysql定位寻址非常快,不会为计算新行的位置做额外的消耗③减少分页和碎片的产生2.2:由于自身使用了uuid索引的内部结构-incrementingidoftherelativeorderofuuid完全没有规则,新行的值不一定大于之前主键的值,所以innodb不可能总是在索引的末尾插入新行,但需要为新行找到一个新行。分配新空间的适当位置。这个过程需要大量的额外操作,数据的无序会导致数据分布分散,会导致以下问题:①:写入的目标页很可能已经刷入磁盘并从缓存中移除,或者还没有加载到缓存中,InnoDB在插入之前要从磁盘中找到目标页并读取到内存中,这会造成大量的随机IO②:因为写的乱序,InnoDB不得不频繁的做pagesplitting操作,为了给新行分配空间,分页导致大量数据被移动,一次插入至少需要修改三页③:由于分页频繁,页面会变得稀疏,填充不规律,会最终导致数据会碎片化。将随机值(uuid和snowflakeid)加载到聚簇索引(innodb默认的索引类型)后,有时需要做一个OPTIMEIZETABLE来重建表和优化页面的填充,这将需要一定的时间消耗。结论:InnoDB应该尽量按照主键自增的顺序插入,尽量使用单调递增的聚簇键值来插入新行,这样不好吗?不会的,自增id还会有以下问题:①:一旦别人爬取了你的数据库,就可以根据数据库的自增id获取到你的业务增长信息,很容易分析出你的业务情况②:对于高并发负载,InnoDB在按主键插入时会造成明显的锁竞争,主键的上界会成为竞争的热点,因为所有的插入都发生在这里,并发插入会导致间隙锁竞争③:Auto_Increment锁机制会造成自增锁抢夺,有一定的性能损耗使用jdbcTemplate测试不同id生成策略在大数据量数据插入时的性能,然后分析mysql索引结构中不同id机制及优势和缺点,并深入解释了为什么使用uuid和randomnon-repeatedid造成数据插入性能损失,详细说明了问题所在。在实际开发中,最好按照mysql官方推荐使用自增id。Mysql博大精深,内部还有很多值得我们去学习优化的地方。