运维同事导入一批大约500万左右的数据,耗时较久。他使用的是纯SQL导入,主键使用的是UUID,因为业务原因没有使用自增ID。

因为是内网,不能远程访问。

通过沟通,大致觉得有两个原因,一是因为UUID作为主键,二是表字段繁多,单行加起来接近10000的长度引起行溢出。

因为是临时一次性任务,同事没有做深纠,我在这里简单做一个验证。

版本:5.7.19

引擎:innodb

uuid主键的影响

mysql自带的UUID()函数简单方便,不重复。但是它缺点也是众所周知的。

  • UUID的返回值通常是随机的,而InnoDB的表实质是以主键组织存储的索引,插入新的记录不是顺序追加,而会往前插入,造成页分裂,表的再平衡。在数据量越大的情况,性能影响越严重。

  • 主键包含在每个二级索引中,过长的主键会浪费磁盘和内存的空间。

页分裂

为什么主键ID的顺序这么重要?

mysql innodb引擎数据结构为B+tree,查找的时候二分查找,这就要求数据是按顺序存储的。

而UUID是无序的,它作为主键在写入的时候,就可能会频繁的进行中间插入和页分裂。页分裂会造成已有的数据移位,类似于arraylist进行插入数据。

因此表数据越多,插入的效率就会越低。也会使得磁盘空间利用率降低。

除了UUID,其它还有两种常见的ID生成法。

一是自增id,最简单效率也最高,但不适合分布式唯一主键,和在一些敏感业务如订单注册用户时,可能通过ID值会暴露一些商业秘密。

雪花算法

再一个是雪花算法,最常见的分布式ID生成算法。

可以看到它跟时间戳(毫秒级)和服务器相关,通过12bit序列号区分同毫秒内产生的不同id。

因此能保证在单台服务器上的大致趋势递增。 在并发高的情况下不能保证完全递增,因此需考虑到这一点。

它有时钟回拨的问题,这里不展开。

定量验证

以上只是定性的判断。

下面做一下定量的测试。

本文验证自增id,雪花算法ID,以及uuid作为主键3种情况,往3张空表插入500万条数据,每个批次10万,共50个批次。

        StopWatch stopWatch = new StopWatch();
stopWatch.start("准备数据阶段");
int count = 500 * 10000 ;
List<TUser> list = new ArrayList<>(count);
ExecutorService executorService = Executors.newFixedThreadPool(100);
List<TUser> finalList = list;
Semaphore semaphore = new Semaphore(100);
for (int i = 0; i < count; i++) {
executorService.submit(new Runnable() {
@SneakyThrows
@Override
public void run() {
semaphore.acquire();
TUser user = new TUser();
// 雪花算法ID
user.setId(SnowflakeIdUtil.snowflakeId());
user.setName(testService.getRandomName());
// UUID
user.setId(UUID.randomUUID().toString());
finalList.add(user);
semaphore.release();
}
}).get();
} stopWatch.stop(); System.out.println("开始写入"); List<Time> times = new ArrayList<>();
stopWatch.start("写入数据阶段");
List<TUser> subList = new ArrayList<>(100000);
for (int i = 1; i <= count; i++) {
TUser user = finalList.get(i-1);
subList.add(user);
if (i % 100000 == 0) {
// finalList.remove(user);
long start = System.currentTimeMillis();
tUserDao.insertBatch(subList);
long time = System.currentTimeMillis() - start;
times.add(new Time(time));
subList.clear();
log.info("写入一次" + i);
}
} stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
// 将每个批次耗时打印出来,放入excel生成图表
times.stream().map(e -> e.getTime()).forEach(System.out::println);

总耗时情况

UU ID:414321ms

雪花ID 167748ms

自增ID 75829ms

将最后每个批次耗时的集合打印出来,放到Excel中,生成图表。

纵坐标表示耗时,单位ms

横坐标表示写入批次

可以明显的观察到,使用UUID作为主键,在表数据量在350万以后,耗时上升极快,性能呈指数级下降。

同时自增ID效率高于雪花算法ID,都很稳定。

这是在行数据量极小(<50)的情况下。

在行大小> 8000,在页16KB至少两行数据的情况下,也必然会发生行溢出。这里再看看测试情况。

行溢出情况

MySQL单行数据最大能存储65535字节的数据,InnoDB的页为16KB,即16384字节,当行的实际存储长度超过16384/2的长度时,就会行溢出。

为什么是/2,因为页至少需要存储两行数据。

为什么单页至少存储两行数据,如果为1,B+tree就退化为了链表。

当行溢出的时候,会将大字段数据存放在页类型为Uncompress BLOB页中,在当前页就会对大字段建立一个引用,类假于二级索引。

所以设置测试表name长度大于大约16384/2的时候,就会发生行溢出现象。

行溢出时,3种不同ID的表现

测试代码跟上面类似。

再准备3张空表。

字段类型varchar,长度10000,user.setName()设置实际长度大于8000。

这次只写入50万条数据,每批次1万条数据,共50个批次。

这次uuid在写入30万条记录后,耗时开始明显上升。

以上是行溢出的情况下,UUID,雪花算法,自增ID等 3种ID的写入性能情况。

uuid在溢出和不溢出下的表现

下面来测试一下,使用UUID在行溢出和不溢出的两种情况下写入性能情况。

再新建两张表,t_user_uuid_long,此表name字段长度100000,最终写入字符长度9995

t_user_uuid_short,此表name字段长度8000,最终写入字符长度7995

写入耗时曲线大致相当,看不出明显差距。

但是读的差距非常明显!

因为t_user_uuid_long 中的name字段发生了行溢出,。。。。。select的时候需要跨页提取数据,类似于做了1次回表,而且回表还是跨页的。

select id,name from t_user_uuid_short order by id asc limit 1000

耗时0.145s

select id,name from t_user_uuid_long order by id asc limit 1000

耗时4.153s,性能相差接近30倍。

小结

以上测试结果只是在测试机上的粗糙测试结果,不是基准测试,只做参考。

测试结果表明,运维同事的慢操作可能受UUID影响 ,但大字段对写入的影响并不明显。

不过大字段对读取的性能影响明显。

MySQL uuid及其相关的一些简单性能测试的更多相关文章

  1. Greenplum 简单性能测试与分析

    如今,多样的交易模式以及大众消费观念的改变使得数据库应用领域不断扩大,现代的大型分布式应用系统的数据膨胀也对数据库的海量数据处理能力和并行处理能力提出了更高的要求,如何在数据呈现海量扩张的同时提高处理 ...

  2. PHP对MySQL数据库的相关操作

    一.Apache服务器的安装 <1>安装版(计算机相关专业所用软件---百度云链接下载)-直接install<2>非安装版(https://www.apachehaus.com ...

  3. [转载]MySQL UUID() 函数

    目录 目录 一 引子 二 MySQL UUID() 函数 三 复制中的 UUID()四 UUID_SHORT() 函数 3.1 实验环境介绍 3.2 搭建复制环境 3.3 基于 STATEMENT 模 ...

  4. 常用Mysql存储引擎--InnoDB和MyISAM简单总结

    常用Mysql存储引擎--InnoDB和MyISAM简单总结 2013-04-19 10:21:52|  分类: CCST|举报|字号 订阅     MySQL服务器采用了模块化风格,各部分之间保持相 ...

  5. MySQL的日志相关内容

    本篇文章介绍一下mysql的备份和日志,由于备份时需要用到日志,所以在讲备份前,如果日志内容篇幅过长,将会把日志和备份分开单独来讲,先简单介绍一下mysql的日志相关内容. MySQL日志 日志是my ...

  6. mysql原理以及相关优化

    说起MySQL的查询优化,相信大家积累一堆技巧:不能使用SELECT *.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?是否理解其背后的工作原理?在 ...

  7. MySQL Query Cache 相关的问题

    最近经常有人问我 MySQL Query Cache 相关的问题,就整理一点 MySQL Query Cache 的内容,以供参考. 顾名思义,MySQL Query Cache 就是用来缓存和 Qu ...

  8. snaic和tornado的简单性能测试

    操作系统 : CentOS7.3.1611_x64 Python 版本 : 3.6.8 tornado版本:6.0.2 snaic版本:19.9.0 CPU : Intel(R) Core(TM) i ...

  9. 我的MYSQL学习心得(一) 简单语法

    我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  10. python操作mysql数据库的相关操作实例

    python操作mysql数据库的相关操作实例 # -*- coding: utf-8 -*- #python operate mysql database import MySQLdb #数据库名称 ...

随机推荐

  1. 使用ASP.NET CORE SignalR实现APP扫描登录

    使用signalr实现APP扫码登录 1. 背景介绍 在移动化时代,web开发很多时候都会带着移动端开发,这个时候为了减少重复输入账号密码以及安全性,很多APP端都会提供一个扫码登录功能,web端生成 ...

  2. java数组排序及查找方法

    前言 在上一篇文章中,壹哥给大家讲解了数组的扩容.缩容及拷贝方式.接下来在今天的文章中,会给大家讲解更重要的数组排序及查找方法.今天的内容会有点难,希望你不要因此而退缩,挺过这一关,你会向上突破的! ...

  3. Java面试——消息队列

    一.消息队列的使用场景 ☞ 以下介绍消息队列在实际应用常用的使用场景.异步处理.应用解耦.流量削锋和消息通讯四个场景.[1]异步处理:场景说明:用户注册后,需要发注册邮件和注册短信.  引入消息队列后 ...

  4. jQ的事件

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. python选出一定数量的随机文件到某个文件夹

    import os import random import shutil def move_file(target_path, save_path, number): file_list = os. ...

  6. 靶机渗透【billu_b0x】

    ip扫描 访问80端口 目录扫描 逐个访问 上传一个图片马,结果没有回显 显示file参数为空.请在"文件"参数中提供文件路径 打开发现有用户名 ![] 发现数据库连接的配置信息, ...

  7. MySQL(十)表空间结构:区、段与碎片区

    表空间结构:区.段与碎片区 为什么要有区? ​ B+树中的每一层的页都会形成一个双向链表,双向链表之间的物理位置可能会离得非常远,当遇到范围查询的适用场景的时候,就会定位到最左边和最右边的记录,然后沿 ...

  8. GPT-4:思考的曙光还是数据的缩影?

    海盗分金,GPT-4初露锋芒 GPT系列模型横空出世后,其是否真实具有思考和推理的能力一直被业界关注.GPT-3.5在多条狗问题和海盗分金问题上表现糟糕.GPT-4在这两个谜题上给出的答案令人惊喜,甚 ...

  9. React中图片的相对路径引入和绝对路径引入

    React中当在JSX中的img标签中引入时使用相对路径引入,地址是基于index.html的而不是当前jsx文件的,如 <img src="./src/assets/images/g ...

  10. ChatGPT在工业领域的研究与应用探索-数据与工况认知

    1.      ChatGPT发展现状... 2 2.      ChatGPT如何与工业相结合... 2 3.      ChatGPT在工业领域的研究与应用... 3 1.   ChatGPT发展 ...