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中你可能忽略的COLLATION实例详解
May 12 MySQL
MySQL 逻辑备份与恢复测试的相关总结
May 14 MySQL
MySQL 使用事件(Events)完成计划任务
May 24 MySQL
MySQL 查询速度慢的原因
May 25 MySQL
MySQL 使用索引扫描进行排序
Jun 20 MySQL
详细聊聊关于Mysql联合查询的那些事儿
Oct 24 MySQL
JMeter对MySQL数据库进行压力测试的实现步骤
Jan 22 MySQL
MySQL库表太大怎么办? 数据库分库分表项目实践
Apr 11 MySQL
MySQL外键约束(Foreign Key)案例详解
Jun 28 MySQL
MySQL提升大量数据查询效率的优化神器
Jul 07 MySQL
mysql sock文件存储了什么信息
Jul 15 MySQL
了解MySQL查询语句执行过程(5大组件)
Aug 14 MySQL
你真的会用Mysql的explain吗
MySQL限制查询和数据排序介绍
MySQL学习必备条件查询数据
mysql中数据库覆盖导入的几种方式总结
Mysql如何实现不存在则插入,存在则更新
Mar 25 #MySQL
MySQL插入数据与查询数据
mysql insert 存在即不插入语法说明
Mar 25 #MySQL
You might like
关于IIS php调用com组件的权限问题
2012/01/11 PHP
PHP中if和or运行效率对比
2014/12/12 PHP
PHP实现移除数组中为空或为某值元素的方法
2017/01/07 PHP
Laravel中日期时间处理包Carbon的简单使用
2017/09/21 PHP
laravel 获取某个查询的查询SQL语句方法
2019/10/12 PHP
jquery 仿QQ校友的DIV模拟窗口效果源码
2010/03/24 Javascript
jQuery中需要注意的细节问题小结
2011/12/06 Javascript
基于jquery的固定表头和列头的代码
2012/05/03 Javascript
怎样在JavaScript里写一个swing把数据插入数据库
2012/12/10 Javascript
JS获取后台Cookies值的小例子
2013/03/04 Javascript
PHP结合jQuery实现红蓝投票功能特效
2015/07/22 Javascript
Nodejs进阶:基于express+multer的文件上传实例
2016/11/21 NodeJs
URL中“#” “?” &amp;“”号的作用浅析
2017/02/04 Javascript
快速处理vue渲染前的显示问题
2018/03/05 Javascript
vue组件jsx语法的具体使用
2018/05/21 Javascript
[46:42]DOTA2-DPC中国联赛正赛 Aster vs Magma BO3 第二场 3月5日
2021/03/11 DOTA
Python实现冒泡,插入,选择排序简单实例
2014/08/18 Python
Python Tkinter基础控件用法
2014/09/03 Python
python计算两个矩形框重合百分比的实例
2018/11/07 Python
Python+selenium点击网页上指定坐标的实例
2019/07/05 Python
PyCharm使用Docker镜像搭建Python开发环境
2019/12/26 Python
pytorch 实现模型不同层设置不同的学习率方式
2020/01/06 Python
python 解决flask 图片在线浏览或者直接下载的问题
2020/01/09 Python
PyCharm中Matplotlib绘图不能显示UI效果的问题解决
2020/03/12 Python
解决Python在导入文件时的FileNotFoundError问题
2020/04/10 Python
Python析构函数__del__定义原理解析
2020/11/20 Python
python 实现波浪滤镜特效
2020/12/02 Python
html5中为audio标签增加停止按钮动作实现方法
2013/01/04 HTML / CSS
美国亚马逊旗下男装网站:East Dane(支持中文)
2019/09/25 全球购物
超市采购员岗位职责
2014/02/01 职场文书
护理专科毕业生自荐书范文
2014/02/19 职场文书
学习退步检讨书
2014/09/28 职场文书
本溪关门山导游词
2015/02/09 职场文书
检讨书之工作不认真
2019/08/14 职场文书
Pytest中skip skipif跳过用例详解
2021/06/30 Python
css常用字体属性与背景属性介绍
2022/02/28 HTML / CSS