Mysql分析设计表主键为何不用uuid


Posted in MySQL onMarch 31, 2022

一、mysql和程序实例

1.1 建表

要说明这个问题,我们首先来建立三张表

分别是user_auto_key,user_uuid,user_random_key,分别表示自动增长的主键,uuid作为主键,随机key作为主键,其它我们完全保持不变。根据控制变量法,我们只把每个表的主键使用不同的策略生成,而其他的字段完全一样,然后测试一下表的插入速度和查询速度:

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

id自动生成表:

Mysql分析设计表主键为何不用uuid

用户uuid表

Mysql分析设计表主键为何不用uuid

随机主键表:

Mysql分析设计表主键为何不用uuid

1.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 {
 
 
    @Autowired
    private JdbcTemplateService jdbcTemplateService;
 
 
    @Autowired
    private AutoKeyTableService autoKeyTableService;
 
 
    @Autowired
    private UUIDKeyTableService uuidKeyTableService;
 
 
    @Autowired
    private RandomKeyTableService randomKeyTableService;
 
 
 
 
    @Test
    void 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);
    }

1.3 程序写入结果

user_key_auto写入结果:

Mysql分析设计表主键为何不用uuid

user_random_key写入结果:

Mysql分析设计表主键为何不用uuid

user_uuid表写入结果:

Mysql分析设计表主键为何不用uuid

1.4 效率测试结果

Mysql分析设计表主键为何不用uuid

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

Mysql分析设计表主键为何不用uuid

可以看出在数据量100W左右的时候,uuid的插入效率垫底,并且在后续增加了130W的数据,uuid的时间又直线下降。

时间占用量总体可以打出的效率排名为:auto_key>random_key>uuid,uuid的效率最低,在数据量较大的情况下,效率直线下滑。

那么为什么会出现这样的现象呢?带着疑问,我们来探讨一下这个问题:

二、使用uuid和自增id的索引结构对比

2.1 使用自增id的内部结构

Mysql分析设计表主键为何不用uuid

自增的主键的值是顺序的,所以Innodb把每一条记录都存储在一条记录的后面。当达到页面的最大填充因子时候(innodb默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的修改):

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

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

③. 减少了页分裂和碎片的产生

2.2 使用uuid的索引内部结构

Mysql分析设计表主键为何不用uuid

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

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

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

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

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

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

结论:使用innodb应该尽可能地按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行

2.3 使用自增id的缺点

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

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

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

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

附:

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

三、总结

本篇博客首先从开篇地提出问题,建表到使用jdbcTemplate去测试不同id的生成策略在大数据量的数据插入表现,然后分析了id的机制不同在mysql的索引结构以及优缺点,深入的解释了为何uuid和随机不重复id在数据插入中的性能损耗,详细的解释了这个问题。

在实际的开发中还是根据mysql的官方推荐最好使用自增id,mysql博大精深,内部还有很多值得优化的点需要我们学习。

到此这篇关于Mysql分析设计表主键为何不用uuid的文章就介绍到这了,更多相关Mysql 设计表 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL中出现乱码问题的终极解决宝典
May 26 MySQL
mysql中between的边界,范围说明
Jun 08 MySQL
浅谈MySQL之浅入深出页原理
Jun 23 MySQL
解决Mysql的left join无效及使用的注意事项说明
Jul 01 MySQL
MySQL一些常用高级SQL语句
Jul 03 MySQL
MySQL令人大跌眼镜的隐式转换
Aug 23 MySQL
关于mysql中时间日期类型和字符串类型的选择
Nov 27 MySQL
千万级用户系统SQL调优实战分享
Mar 03 MySQL
MySQL去除密码登录告警的方法
Apr 20 MySQL
mysql 8.0.27 绿色解压版安装教程及配置方法
Apr 20 MySQL
MySQL提升大量数据查询效率的优化神器
Jul 07 MySQL
MySQL事务的隔离级别详情
Jul 15 MySQL
你真的会用Mysql的explain吗
MySQL限制查询和数据排序介绍
MySQL学习必备条件查询数据
mysql中数据库覆盖导入的几种方式总结
Mysql如何实现不存在则插入,存在则更新
Mar 25 #MySQL
MySQL插入数据与查询数据
mysql insert 存在即不插入语法说明
Mar 25 #MySQL
You might like
PHP文件上传原理简单分析
2011/05/29 PHP
CentOS下与Apache连接的PHP多版本共存方案实现详解
2015/12/19 PHP
php的对象传值与引用传值代码实例讲解
2021/02/26 PHP
Prototype 学习 工具函数学习($方法)
2009/07/12 Javascript
JavaScript toFixed() 方法
2010/04/15 Javascript
js open() 与showModalDialog()方法使用介绍
2013/09/10 Javascript
javascript数组操作方法小结和3个属性详细介绍
2014/07/05 Javascript
浅谈JavaScript数据类型
2015/03/03 Javascript
js实现上一页下一页的效果【附代码】
2016/03/10 Javascript
基于Bootstrap实现tab标签切换效果
2020/04/15 Javascript
vue-cli的eslint相关用法
2017/09/29 Javascript
解决vue页面刷新或者后退参数丢失的问题
2018/03/13 Javascript
AngularJS 多指令Scope问题的解决
2018/10/25 Javascript
基于iview-admin实现动态路由的示例代码
2019/10/02 Javascript
Vue中jsx不完全应用指南小结
2019/11/01 Javascript
实例讲解JavaScript 计时事件
2020/07/04 Javascript
Vue页面渲染中key的应用实例教程
2021/01/12 Vue.js
Vue项目打包部署到apache服务器的方法步骤
2021/02/01 Vue.js
Python开发的单词频率统计工具wordsworth使用方法
2014/06/25 Python
Python语言生成水仙花数代码示例
2017/12/18 Python
Python中实现最小二乘法思路及实现代码
2018/01/04 Python
python 通过xml获取测试节点和属性的实例
2018/03/31 Python
python tornado微信开发入门代码
2018/08/24 Python
Django REST framework内置路由用法
2019/07/26 Python
pycharm设置python文件模板信息过程图解
2020/03/10 Python
Python多线程通信queue队列用法实例分析
2020/03/24 Python
vscode+PyQt5安装详解步骤
2020/08/12 Python
Python之字典添加元素的几种方法
2020/09/30 Python
CLR与IL分别是什么含义
2016/08/23 面试题
亿阳信通股份有限公司C#笔试题
2016/12/06 面试题
建筑工程专业毕业生自荐信
2013/10/19 职场文书
物理力学求职信
2014/02/18 职场文书
五一口号
2014/06/19 职场文书
装修活动策划方案
2014/08/27 职场文书
2015小学教师年度考核工作总结
2015/05/12 职场文书
小学班主任工作随笔
2015/08/15 职场文书