用雪花 id 和 uuid 还是自增id做 MySQL 主键

MySQL中设计表的时候,MySQL官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究竟有什么坏处?

MySQL和程序实例
1、要说明这个问题,我们首先来建立三张表
分别是user_auto_key,user_uuid,user_random_key,分别表示自动增长的主键,uuid作为主键,随机key作为主键,其它我们完全保持不变。根据控制变量法,我们只把每个表的主键使用不同的策略生成,而其他的字段完全一样,然后测试一下表的插入速度和查询速度:

注:这里的随机key其实是指用雪花算法算出来的前后不连续不重复无规律的id:一串18位长度的long值

id自动生成表:
在这里插入图片描述
用户uuid表
在这里插入图片描述
随机主键表:
在这里插入图片描述
2、使用spring的jdbcTemplate来实现增查测试
技术框架:

springboot+jdbcTemplate+junit+hutool,

程序的原理就是连接自己的测试数据库,然后在相同的环境下写入同等数量的数据,来分析一下insert插入的时间来进行综合其效率,为了做到最真实的效果,所有的数据采用随机生成,比如名字、邮箱、地址都是随机生成

package com.wyq.mysqldemo;
import cn.hutool.core.collection.CollectionUtil;
import com.wyq.mysqldemo.databaseobject.UserKeyAuto;
import com.wyq.mysqldemo.databaseobject.UserKeyRandom;
import com.wyq.mysqldemo.databaseobject.UserKeyUUID;
import com.wyq.mysqldemo.diffkeytest.AutoKeyTableService;
import com.wyq.mysqldemo.diffkeytest.RandomKeyTableService;
import com.wyq.mysqldemo.diffkeytest.UUIDKeyTableService;
import com.wyq.mysqldemo.util.JdbcTemplateService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;
import java.util.List;
@SpringBootTest
class MysqlDemoApplicationTests {@Autowiredprivate JdbcTemplateService jdbcTemplateService;@Autowiredprivate AutoKeyTableService autoKeyTableService;@Autowiredprivate UUIDKeyTableService uuidKeyTableService;@Autowiredprivate RandomKeyTableService randomKeyTableService;@Testvoid testDBTime() {StopWatch stopwatch = new StopWatch("执行sql时间消耗");/*** auto_increment key任务*/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);System.out.println(insertResult);}long end1 = System.currentTimeMillis();System.out.println("auto key消耗的时间:" + (end1 - start1));stopwatch.stop();/*** uudID的key*/final String insertSql2 = "INSERT INTO user_uuid(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)";List<UserKeyUUID> insertData2 = uuidKeyTableService.getInsertData();stopwatch.start("UUID的key表任务开始");long begin = System.currentTimeMillis();if (CollectionUtil.isNotEmpty(insertData)) {boolean insertResult = jdbcTemplateService.insert(insertSql2, insertData2, true);System.out.println(insertResult);}long over = System.currentTimeMillis();System.out.println("UUID key消耗的时间:" + (over - begin));stopwatch.stop();/*** 随机的long值key*/final String insertSql3 = "INSERT INTO user_random_key(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)";List<UserKeyRandom> insertData3 = randomKeyTableService.getInsertData();stopwatch.start("随机的long值key表任务开始");Long start = System.currentTimeMillis();if (CollectionUtil.isNotEmpty(insertData)) {boolean insertResult = jdbcTemplateService.insert(insertSql3, insertData3, true);System.out.println(insertResult);}Long end = System.currentTimeMillis();System.out.println("随机key任务消耗时间:" + (end - start));stopwatch.stop();String result = stopwatch.prettyPrint();System.out.println(result);}
}

3、程序写入结果
user_key_auto写入结果:
在这里插入图片描述
user_random_key写入结果:
在这里插入图片描述
user_uuid表写入结果:
在这里插入图片描述
4、效率测试结果

在这里插入图片描述
在已有数据量为130W的时候:我们再来测试一下插入10w数据,看看会有什么结果:

在这里插入图片描述
可以看出在数据量100W左右的时候,uuid的插入效率垫底,并且在后序增加了130W的数据,uudi的时间又直线下降。时间占用量总体可以打出的效率排名为: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应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行

3、使用自增id的缺点

那么使用自增的id就完全没有坏处了吗?并不是,自增id也会存在以下几点问题:

①别人一旦爬取你的数据库,就可以根据数据库的自增id获取到你的业务增长信息,很容易分析出你的经营情况

②对于高并发的负载,innodb在按主键进行插入的时候会造成明显的锁争用,主键的上界会成为争抢的热点,因为所有的插入都发生在这里,并发插入会导致间隙锁竞争

③Auto_Increment锁机制会造成自增锁的抢夺,有一定的性能损失

附:

Auto_increment的锁争抢问题,如果要改善需要调优innodb_autoinc_lock_mode的配置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/2411.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3.跑马灯

1.GPIO的输出模式&#xff1a; 推挽模式输出&#xff1a;因为LED0和LED1阳极都是3.3V&#xff0c;需要将阴极设置为低电平才可以点亮LED&#xff1b;操作io口时&#xff0c;必须引入源文件和头文件&#xff1b;关于时钟的文件存放在rcc中&#xff1b; 2.GPIO库函数介绍&#…

NodeJS NVM版本管理⑩⑧

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言NVM IntroduceNVM UseDownload And InstallNVM Project Use总结 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹…

使用VSCODE跑orbslam2踩的坑

我用的是ubuntu22.04&#xff0c;opencv是4.7&#xff0c;使用其他的库感觉就算版本不一样&#xff0c;也能跑。 一、运行build.sh能够产生可执行文件遇到的问题 1.由于opencv版本高带来的问题 这些问题怎么定位出现在哪些文件中&#xff0c;你通过命令行&#xff0c;运行下…

【K8SRockyLinux】基于开源操作系统搭建K8S高可用集群(详细版)

文章目录 一、实验节点规划表&#x1f447;二、实验版本说明&#x1f4c3;三、实验拓扑&#x1f4ca;四、实验详细操作步骤&#x1f579;️1. 安装Rocky Linux开源企业操作系统2. 所有主机系统初始化3. 所有master节点部署keepalived4. 所有master节点部署haproxy5. 所有节点配…

【SpringMVC 学习笔记】

SpringMVC 笔记记录 1. SpringMVC 简介2. 入门案例3. 基本配置3.1 xml形式配置3.2 注解形式配置 4. 请求4.1 请求参数4.1.1 普通类型传参4.1.2 实体类类型传参4.1.3 数组和集合类型传参 4.2 类型转换器4.3 请求映射 5. 响应 1. SpringMVC 简介 三层架构 2. 入门案例 3. 基本…

Java死锁

代码&#xff1a; package Test0629;public class BankTest {static Bank b1 null;static Bank b2 null;public static void main(String[] args) {Thread t1 new Thread(){public void run(){b1 Bank.getInstance();}};Thread t2 new Thread(){public void run(){b2 Ba…

fast admin报错:Unexpected token ‘<‘, “ (已解决)

报错信息&#xff1a; 在新加视图的时候的&#xff0c;点击编辑/添加忽然报这个错误&#xff0c;网上找了半天&#xff0c;js、视图、修修改改最后竟是一个小问题&#xff1b; 解决方法&#xff1a; 改为&#xff1a; 简单的说就是&#xff1a;btn-ajax ->btn-dialog

【MySQL】MySQL数据库,RDBMS 术语,使用说明和报错解决的详细讲解

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

【强化学习】常用算法之一 “SAC”

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

【网络安全带你练爬虫-100练】第1练:发送请求获取返回内容

目录 一、前置知识1 二、前置知识2 requests库的7个主要方法 语法&#xff1a; 注解&#xff1a; 三、扩展工具 四、网络安全小圈子 一、前置知识1 顾名思义网络爬虫第一步&#xff0c;爬取目标 URL的网页的信息 可以使用 urllib.request 和 requests发送请求&#xff0…

浪潮发布G2平台

2017年2月28日下午&#xff0c;浪潮在北京粤财JW万豪酒店以“智变”为主题&#xff0c;发布新一代智能存储平台G2&#xff0c;该平台基于统一架构和In系列智能软件设计&#xff0c;在保障”三高”特性满足企业级关键数据存储、处理需求的同时&#xff0c;更强调数据生命周期的智…

了解浏览器缓存

什么是HTTP缓存&#xff0c;如何工作的&#xff1f; 当我们打开一个页面时&#xff0c;会向服务端发起很多次请求&#xff0c;如下图打开百毒首页&#xff0c;发起了HTML、各种图片、JS、CSS等资源共101次请求。这里面很多资源并不会频繁变化&#xff0c;每次打开页面都重新请…