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 10 MySQL
Mysql Online DDL的使用详解
May 20 MySQL
MySQL数据库压缩版本安装与配置详细教程
May 21 MySQL
MySQL 数据类型选择原则
May 27 MySQL
MySQL注入基础练习
May 30 MySQL
MySQL8.0.18配置多主一从
Jun 21 MySQL
MYSQL 表的全面总结
Nov 11 MySQL
MySQL Innodb索引机制详细介绍
Nov 23 MySQL
MySQL派生表联表查询实战过程
Mar 20 MySQL
MySQL示例讲解数据库约束以及表的设计
Jun 16 MySQL
Mysql表数据比较大情况下修改添加字段的方法实例
Jun 28 MySQL
MySQL事务的隔离级别详情
Jul 15 MySQL
你真的会用Mysql的explain吗
MySQL限制查询和数据排序介绍
MySQL学习必备条件查询数据
mysql中数据库覆盖导入的几种方式总结
Mysql如何实现不存在则插入,存在则更新
Mar 25 #MySQL
MySQL插入数据与查询数据
mysql insert 存在即不插入语法说明
Mar 25 #MySQL
You might like
解析CodeIgniter自定义配置文件
2013/06/18 PHP
php中如何同时使用session和cookie来保存用户登录信息
2013/07/05 PHP
深入解读php中关于抽象(abstract)类和抽象方法的问题分析
2014/01/03 PHP
thinkphp3.2中Lite文件替换框架入口文件或应用入口文件的方法
2015/05/21 PHP
关于ThinkPhp 框架表单验证及ajax验证问题
2017/07/19 PHP
php json转换相关知识(小结)
2018/12/21 PHP
php ActiveMQ的安装与使用方法图文教程
2020/02/23 PHP
javascript页面上使用动态时间具体实现
2014/03/18 Javascript
JavaScript实现在页面间传值的方法
2015/04/07 Javascript
Javascript HTML5 Canvas实现的一个画板
2020/04/12 Javascript
AngularJS优雅的自定义指令
2016/07/01 Javascript
AngularJS基于ngInfiniteScroll实现下拉滚动加载的方法
2016/12/14 Javascript
js实现可输入可选择的select下拉框
2016/12/21 Javascript
使用vue-router与v-if实现tab切换遇到的问题及解决方法
2018/09/07 Javascript
Angular 实现输入框中显示文章标签的实例代码
2018/11/07 Javascript
使用Vue实现调用接口加载页面初始数据
2019/10/28 Javascript
python虚拟环境 virtualenv的简单使用
2020/01/21 Javascript
javascript全局自定义鼠标右键菜单
2020/12/08 Javascript
Python实现批量修改文件名实例
2015/07/08 Python
详解python3中socket套接字的编码问题解决
2017/07/01 Python
python+opencv实现摄像头调用的方法
2019/06/22 Python
Python 使用matplotlib模块模拟掷骰子
2019/08/08 Python
python 如何去除字符串头尾的多余符号
2019/11/19 Python
如何使用Cython对python代码进行加密
2020/07/08 Python
爱尔兰电子产品购物网站:Komplett.ie
2018/04/04 全球购物
27个经典Linux面试题及答案,你知道几个?
2014/03/11 面试题
合同专员岗位职责
2013/12/18 职场文书
人事主管岗位职责
2014/01/30 职场文书
士力架广告词
2014/03/20 职场文书
紧急迫降观后感
2015/06/15 职场文书
2015中学学校工作总结
2015/07/20 职场文书
无故旷工检讨书
2015/08/15 职场文书
创业计划书之个人工作室
2019/08/22 职场文书
浅谈CSS不规则边框的生成方案
2021/05/25 HTML / CSS
python开发人人对战的五子棋小游戏
2022/05/02 Python
Win11 Beta 预览版 22621.575 和 22622.575更新补丁KB5016694发布(附更新内容大全)
2022/08/14 数码科技