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入门命令之函数-单行函数-流程控制函数
Apr 05 MySQL
MySQL 角色(role)功能介绍
Apr 24 MySQL
MySQL 查询速度慢的原因
May 25 MySQL
my.ini优化mysql数据库性能的十个参数(推荐)
May 26 MySQL
MySQL8.0升级的踩坑历险记
Nov 01 MySQL
mysql聚集索引、辅助索引、覆盖索引、联合索引的使用
Feb 12 MySQL
一文搞懂MySQL索引页结构
Feb 28 MySQL
mysql的Buffer Pool存储及原理
Apr 02 MySQL
聊聊mysql都有哪几种分区方式
Apr 13 MySQL
mysql使用FIND_IN_SET和group_concat两个方法查询上下级机构
Apr 20 MySQL
MySql数据库 查询时间序列间隔
May 11 MySQL
Mysql索引失效 数据库表中有索引还是查询很慢
May 15 MySQL
你真的会用Mysql的explain吗
MySQL限制查询和数据排序介绍
MySQL学习必备条件查询数据
mysql中数据库覆盖导入的几种方式总结
Mysql如何实现不存在则插入,存在则更新
Mar 25 #MySQL
MySQL插入数据与查询数据
mysql insert 存在即不插入语法说明
Mar 25 #MySQL
You might like
php截取utf-8中文字符串乱码的解决方法
2010/03/29 PHP
PHP5.4中json_encode中文转码的变化小结
2013/01/30 PHP
基于PHP技术开发客服工单系统
2016/01/06 PHP
PHP的数组中提高元素查找与元素去重的效率的技巧解析
2016/03/03 PHP
PHP中file_put_contents追加和换行的实现方法
2017/04/01 PHP
CI框架(CodeIgniter)实现的导入、导出数据操作示例
2018/05/24 PHP
默认让页面的第一个控件选中的javascript代码
2009/12/26 Javascript
Javascript 静态页面实现随机显示广告的办法
2010/11/17 Javascript
jquer之ajaxQueue简单实现代码
2011/09/15 Javascript
利用cookie记住背景颜色示例代码
2013/11/04 Javascript
使用百度地图api实现根据地址查询经纬度
2014/12/11 Javascript
JavaScript日期时间与时间戳的转换函数分享
2015/01/31 Javascript
浅谈JavaScript数据类型及转换
2015/02/28 Javascript
JavaScript的Backbone.js框架的一些使用建议整理
2016/02/14 Javascript
js实现带农历和八字等信息的日历特效
2016/05/16 Javascript
vue2.0 实现页面导航提示引导的方法
2018/03/13 Javascript
vue2实现搜索结果中的搜索关键字高亮的代码
2018/08/29 Javascript
vue 本地环境跨域请求proxyTable的方法
2018/09/19 Javascript
vue下载二进制流图片操作
2020/10/26 Javascript
Vue路由权限控制解析
2020/11/09 Javascript
python下如何查询CS反恐精英的服务器信息
2017/01/17 Python
Python统计python文件中代码,注释及空白对应的行数示例【测试可用】
2018/07/25 Python
python读取文件名并改名字的实例
2019/01/07 Python
python爬取基于m3u8协议的ts文件并合并
2019/04/26 Python
Black Halo官方网站:购买连衣裙、礼服和连体裤
2018/06/13 全球购物
Guess美国官网:美国知名服装品牌
2019/04/08 全球购物
木工主管岗位职责
2013/12/08 职场文书
实习教师自我鉴定
2013/12/12 职场文书
2014迎新年晚会策划方案
2014/02/23 职场文书
寄语是什么意思
2014/04/10 职场文书
领导干部作风建设工作总结
2014/10/23 职场文书
2014年医院科室工作总结
2014/12/20 职场文书
2015年会计年终工作总结
2015/05/26 职场文书
2015年六年级班主任工作总结
2015/10/15 职场文书
基于HTML十秒做出淘宝页面
2021/10/24 HTML / CSS
Nginx如何配置根据路径转发详解
2022/07/23 Servers