⾃增还是UUID?数据库主键的类型选择,为啥不能⽤uuid做MySQL的
主键?
⼀、⾃增还是UUID?数据库主键的类型选择
⾃增还是UUID?这个问题看似简单,但是能诱发很多思考,也涉及到了很多细节。先说下uuid和 auto_increment(数据库⾃增主键)的优缺点吧,因为是个⼈理解,如有错误恳请指出:jdbctemplate查询一条数据
1、⾃增主键
⾃增ID是在设计表时将id字段的值设置为⾃增的形式,这样当插⼊⼀⾏数据时⽆需指定id会⾃动根据前⼀字段的ID值+1进⾏填充。在MySQL数据库中,可通过sql语句AUTO_INCREMENT来对特定的字段启⽤⾃增赋值使⽤⾃增ID作为主键,能够保证字段的原⼦性。
auto_increment优点:
字段长度较uuid⼩很多,可以是bigint甚⾄是int类型,这对检索的性能会有所影响。我们平时数据库⼀般⽤的都是innodb引擎的表,这种表格检索数据的时候,哪怕⾛索引,也是先根据索引到主键,然后由主键到这条记录。所以主键的长度短的话,读性能是会好⼀点的。
在写的⽅⾯,因为是⾃增的,所以主键是趋势⾃增的,也就是说新增的数据永远在后⾯,这点对于性能有很⼤的提升(这点我接下来会在uuid的优缺点分析中解释,虽然⽤词可能不太专业)
数据库⾃动编号,速度快,⽽且是增量增长,按顺序存放,对于检索⾮常有利;
数字型,占⽤空间⼩,易排序,在程序中传递也⽅便;
如果通过⾮系统增加记录时,可以不⽤指定该字段,不⽤担⼼主键重复问题。
auto_incremen的缺点:
最致命的⼀个缺点就是,很容易被别⼈知晓业务量,然后很容易被⽹络爬⾍教做⼈
⾼并发的情况下,竞争⾃增锁会降低数据库的吞吐能⼒
数据迁移的时候,特别是发⽣表格合并这种操作的时候,会⾮常蛋疼
因为⾃动增长,在⼿动要插⼊指定ID的记录时会显得⿇烦,尤其是当系统与其它系统集成时,需要数据导⼊时,很难保证原系统的ID不发⽣主键冲突(前提是⽼系统也是数字型的)。特别是在新系统上线时,新旧系统并⾏存在,并且是异库异构的数据库的情况下,需要双向同步时,⾃增主键将是你的噩梦;
在系统集成或割接时,如果新旧系统主键不同是数字型就会导致修改主键数据类型,这也会导致其它有外键关联的表的修改,后果同样很严重;若系统也是数字型的,在导⼊时,为了区分新⽼数据,可能想在⽼数据主键前统⼀加⼀个字符标识(例如“o”,old)来表⽰这是⽼数据,那么⾃动增长的数字型⼜⾯临⼀个挑战。
2、UUID
UUID含义是通⽤唯⼀识别码 (Universally Unique Identifier),指在⼀台机器上⽣成的数字,它保证对在同⼀时空中的所有机器都是唯⼀的。通常平台会提供⽣成的API。换句话说能够在⼀定的范围内保证主键id的唯⼀性。
优点:
地球唯⼀的guid,绝对不会冲突。数据拆分、合并存储的时候,能达到全局的唯⼀性
可以在应⽤层⽣成,提⾼数据库吞吐能⼒
是string类型,写代码的时候⽅便很多
缺点:
影响插⼊速度,并且造成硬盘使⽤率低。与⾃增相⽐,最⼤的缺陷就是随机io。这⼀点⼜要谈到我们的innodb了,因为这个默认引擎,表中数据是按照主键顺序存放的。也就是说,如果发⽣了随机io,那么就会频繁地移动磁盘块。当数据量⼤的时候,写的短板将⾮常明显。当然,这个缺点可以通过nosql那些产品解决。
uuid之间⽐较⼤⼩相对数字慢不少,影响查询速度。
uuid占空间⼤,如果你建的索引越多,影响越严重
读取出来的数据也是没有规律的,通常需要order by,其实也很消耗数据库资源
看起来⽐较丑
⼆、为啥不能⽤uuid做MySQL的主键?
在 MySQL 中设计表的时候,MySQL 官⽅推荐不要使⽤ uuid 或者不连续不重复的雪花 id(long 形且唯⼀,单机递增),⽽是推荐连续⾃增的主键 id,官⽅的推荐是 auto_increment。
那么为什么不建议采⽤ uuid,使⽤ uuid 究竟有什么坏处?要说明这个问题,我们⾸先来建⽴三张表,分别
是:user_auto_key、user_uuid、user_random_key,他们分别表⽰⾃动增长的主键,uuid 作为主键,随机 key 作为主键,其他我们完全保持不变。
根据控制变量法,我们只把每个表的主键使⽤不同的策略⽣成,⽽其他的字段完全⼀样,然后测试⼀下表的插⼊速度和查询速度。
注:这⾥的随机 key 其实是指⽤雪花算法算出来的前后不连续不重复⽆规律的id:⼀串 18 位长度的 long 值。
光有理论不⾏,直接上程序,使⽤ Spring 的 jdbcTemplate 来实现增查测试。
技术框架:Spring Boot+jdbcTemplate+junit+hutool,程序的原理就是连接⾃⼰的测试数据库,然后在相同的环境下写⼊同等数量的数据,来分析⼀下 insert 插⼊的时间来进⾏综合其效率。
为了做到最真实的效果,所有的数据采⽤随机⽣成,⽐如名字、邮箱、地址都是随机⽣成:
程序写⼊效率测试结果
在已有数据量为 130W 的时候:我们再来测试⼀下插⼊ 10w 数据,看看会有什么结果:
可以看出在数据量 100W 左右的时候,uuid 的插⼊效率垫底,并且在后序增加了 130W 的数据,uuid 的时间⼜直线下降。
时间占⽤量总体可以打出的效率排名为:auto_key>random_key>uuid。
uuid 的效率最低,在数据量较⼤的情况下,效率直线下滑。那么为什么会出现这样的现象呢?带着疑问,我们来探讨⼀下这个问题:
三、使⽤ uuid 和⾃增 id 的索引结构对⽐
1、使⽤⾃增 id 的内部结构
⾃增的主键的值是顺序的,所以 InnoDB 把每⼀条记录都存储在⼀条记录的后⾯。
当达到页⾯的最⼤填充因⼦时候(InnoDB 默认的最⼤填充因⼦是页⼤⼩的 15/16,会留出 1/16 的空间留作以后的修改)。
下⼀条记录就会写⼊新的页中,⼀旦数据按照这种顺序的⽅式加载,主键页就会近乎于顺序的记录填满,提升了页⾯的最⼤填充率,不会有页的浪费。
新插⼊的⾏⼀定会在原有的最⼤数据⾏下⼀⾏,MySQL 定位和寻址很快,不会为计算新⾏的位置⽽做出额外的消耗。
减少了页分裂和碎⽚的产⽣。
2、使⽤ uuid 的索引内部结构
因为 uuid 相对顺序的⾃增 id 来说是毫⽆规律可⾔的,新⾏的值不⼀定要⽐之前的主键的值要⼤,所以 innodb ⽆法做到总是把新⾏插⼊到索引的最后,⽽是需要为新⾏寻新的合适的位置从⽽来分配新的空间。
这个过程需要做很多额外的操作,数据的毫⽆顺序会导致数据分布散乱,将会导致以下的问题:
写⼊的⽬标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb 在插⼊之前不得不先到并从磁盘读取⽬标页到内存中,这将导致⼤量的随机 IO。
因为写⼊是乱序的,innodb 不得不频繁的做页分裂操作,以便为新的⾏分配空间,页分裂导致移动⼤量的数据,⼀次插⼊最少需要修改三个页以上。
由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎⽚。在把随机值(uuid 和雪花 id)载⼊到聚簇索引(InnoDB 默认的索引类型)以后,有时候会需要做⼀次 OPTIMEIZE TABLE 来重建表并优化页的填充,这将⼜需要⼀定的时间消耗。
结论:使⽤ InnoDB 应该尽可能的按主键的⾃增顺序插⼊,并且尽可能使⽤单调的增加的聚簇键的值来插⼊新⾏。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论