我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!

文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面。

如果你打算好好学习一下 MySQL,性能优化肯定是绕不过去一个问题。当你撸起袖子准备开始的时候,突然发现一个问题摆在眼前,本地数据库中没那么大的数据量啊,几条数据优化个毛线啊。生产库里数据多,但谁敢直接在生产环境动手啊,想被提前优化吗?

要知道,程序员从不轻言放弃,没有数据我们就自己创造数据嘛,new 对象这种事情可是我们的拿手好戏,对象都能 new 出来,更别说几百万条数据了。

使用官方数据

官方显然知道我们需要一些测试数据做个练习什么的,所以准备了一份测试数据给我们。可以到 https://github.com/datacharmer/test_db 上去下载,这个数据库包含约30万条员工记录和280万个薪水条目,文件大小为 167 M。

下载完成之后,直接使用 MySQL 客户端运行 sql 文件即可。

或者直接使用命令,然后输入密码导入。

mysql -u root -p < employees.sql

这是最简单的一种方法,只要你能把 sql 文件下载下来就可以了。但是数据量不够大,员工表才 30 万条数据,还不够百万级别,而且字段都是定义好的,不能灵活定制。

背景说明

创建百万级数据的方式,要到达的目的有两点:

  1. 定制比较灵活,不能只是一两个字段了事,那没什么实际意义。
  2. 速度快,不能说弄个几百万数据好几个小时甚至更长,那不能接收。

本次目标是创建两个表,一个用户表,另外一个订单表,当然没有真实环境中的表字段那么多,但是对于学习测试来说差不多够了。

两个表的表结构如下:

# 用户表
CREATE TABLE `user` (
`id` varchar(36) NOT NULL,
`user_name` varchar(12) DEFAULT NULL,
`age` tinyint(3) DEFAULT NULL,
`phone` varchar(11) DEFAULT NULL,
`province` varchar(10) DEFAULT NULL,
`city` varchar(10) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; # 订单表
CREATE TABLE `order` (
`id` varchar(36) NOT NULL,
`user_id` varchar(36) DEFAULT NULL,
`product_count` int(11) DEFAULT NULL,
`price` decimal(10,0) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

用户表(user)创建 500 万条数据,id 使用 uuid,年龄从 1 到 120 随机,电话号码随机 11 位,省份编码和城市编码随机,创建时间和更新时间在某一时间范围内随机。

订单表(order)根据用户表生成,每个用户随机生成 0 到 3 个订单,订单编号采用 uuid,商品数量随机 1 到 5 个,价格随机,创建时间和更新时间在某一时间段内随机。由于每个用户产生 0 到 3 个订单,所以,产生的订单量应该大于 500 万,我在本地跑的时候基本上在 700多万左右。

创建总时间和表的字段个数以及字段的生成算法有直接关系,字段越多、算法越复杂,需要的时间就越多,比如使用 uuid 就比使用自增 id 花费更长时间,随机时间就比直接使用当前时间花费更长时间。

如果只插入 500 万自增 id 这一个字段,十几秒就能完成,但是无论是模拟线上环境还是自学性能优化技巧都没什么意义。

下面就来介绍三种方式来快速创建 500 万用户数据以及大于 500 万的订单数据。

写程序批量插入

作为一个开发人员,当你打算创建百万条数据的时候,大多数时候首先相当的应该就是写程序,毕竟 CURD 我们最拿手了。

用程序的方式插入也分两种情况,第一种就是逐条插入,这也是平时开发中最常用到的方法,直觉上我们可能会认为这样比较快。事实上并不是这样,虽然比起手动一条一条插入是快的多,但是,很有可能你在等待了一段时间后失去耐心,然后结束程序,不管你用哪种数据库连接池都一样,在百万数量级面前仍然慢的离谱。

第二种情况就是使用 MySQL 的批量插入方法,我们都知道 MySQL 支持一次性插入多条记录,就是下面这样的形式。

insert into `table_name` (id,column1) values (1,'value1'),(2,'value2'),(3,'value3');

这样一来,比你一条一条语句执行要快很多,比如 1000 条记录执行一次 insert,一共执行 5000 次即可,如果是一条一条插入呢,那就要执行 500 万次。

由于后面两种方式用到了 Python 生成文件,所以这种方式也用了 Python 实现,实例代码如下。完整代码可在文末给出的 github 上获取。

def insert_data(self):
cursor = self.conn.cursor()
for x in range(5000):
insert_user_sql = """
insert into `user` ( `id`,`user_name`,`phone`,`age`, `province`, `city`, `create_time`,`update_time` )
VALUES(%s,%s,%s,%s,%s,%s,%s,%s)
"""
insert_order_sql = """ insert into `order` ( `id`, `product_count`, `user_id`, `price`, `create_time`, `update_time`)
values(%s,%s,%s,%s,%s,%s)
"""
user_values, order_values = [], []
for i in range(1000):
timestamp = self.randomTimestamp()
time_local = time.localtime(timestamp)
createTime = time.strftime("%Y-%m-%d %H:%M:%S", time_local)
user_id = str(uuid.uuid4())
user_values.append(
(user_id, "名字" + str(x) + str(i), self.createPhone(), random.randint(1, 120),
str(random.randint(1, 26)),
str(random.randint(1, 1000)), createTime, createTime)) random_order_count = random.randint(0, 3)
if random_order_count > 0:
for c in range(random_order_count):
timestamp = self.randomTimestamp()
time_local = time.localtime(timestamp)
order_create_time = time.strftime("%Y-%m-%d %H:%M:%S", time_local)
order_values.append((str(uuid.uuid4()), random.randint(1, 5), user_id,
random.randint(10, 2000), order_create_time, order_create_time))
cursor.executemany(insert_user_sql, user_values)
cursor.executemany(insert_order_sql, order_values)
self.conn.commit() cursor.close()

经过一段时间时间的等待后,运行完成了,整个运行过程耗时 1823 秒,30分钟

最后成功生成用户记录 500 万条,订单记录 749 万多条。

速度还算能接受吧,马马虎虎吧。

再想速度快一点,可以开多线程,我用 5 个线程跑了一下,一个线程插入 100万条,最终最长的线程耗时 1294秒,21分钟,也没快多少,线程个数对时间多少有些影响,但是我没有试。

生成 SQL 脚本

这种方式和上面的方式类似,只不过上面通过程序方式直接将拼接出来的 SQL 语句执行了,而这种方式是将拼接好的 SQL 语句写入文件中。当然还是以一条语句插入多行记录的形式。

insert into `table_name` (id,column1) values (1,'value1'),(2,'value2'),(3,'value3');

写 500 万用户数据,加上随机的订单数据, sql 文件的过程耗时为 696 秒,11分钟左右。

当然这么大数据量拼接出来的脚本文件也很大,用户表脚本 680 多M,订单表脚本 1个G。

最后将写好的这两个文件分别在 MySQL 中执行。

执行用户表脚本,耗时 3 分钟左右。

mysql -uroot -p mast_slave < sql/insert_user_500w.sql

执行订单表脚本,耗时 7 分钟左右,订单量 750 多万个。

mysql -uroot -p mast_slave < sql/insert_order_500w+.sql

一共耗时,20分钟左右,加上中间的手工操作,感觉不如第一种方法中的多线程方式省事。

load data infile 方式

最后这种方式是使用 load data infile 方式,这是 MySQL 提供的一种从文件快速导入的方式。比如按照特定符号分隔,导入对应的字段中。

本文例子中我是按照逗号分隔的,字段之间以逗号分隔,生成 500 万条用户行 和随机订单行。

依然是用 Python 脚本生成文件,生成文件的过程耗时 779 秒,12分钟左右。

两个文件大小分别是 560 多M 和 900 M。

最后执行 load data infile 将文件导入到对应的表中,在执行这个命令后可能会出现下面这个错误提示。

ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

这是因为 MySQL 自身的安全配置所致,需要更改 my.cnf,在其中加入下面的配置,然后重启服务。

secure_file_priv=

等于号后边为空表示允许所有目录下的文件 load,如果要限定某个特定目录,在等于号后边填上对应的文件目录即可。

然后执行下面的语句,将用户记录导入到 user 表。

load data infile '/Users/fengzheng/知识管理/技术写作/mysql/创建测试数据/sql/load_user_txt_500w.txt' replace into table user FIELDS TERMINATED BY ',';

500万条耗时 3分32秒。

将订单记录导入到 order 表。

load data infile '/Users/fengzheng/知识管理/技术写作/mysql/创建测试数据/sql/load_order_txt_500w+.txt' replace into table `order` FIELDS TERMINATED BY ',';

749 万条记录,耗时 8分31秒。

整个过程加起来 24 分钟左右。

最后

好了,现在可以愉快的做各种测试和优化了。

有同学看完可能要说了,20多分钟好像也不算快啊。因为数据量确实比较大,再有数据复杂度和导入时间也有很大关系,如果你只是导入一列自增id,别说 500 万,1000万都用不了一分钟就完成了。

其实还有一点优化空间的,比如说把数据库引擎改成 MYISAM 会更快一些,尤其是对于批量插入的情景,但是插入完成后还要再改回来,也需要耗费一些时间,而且来回切换也比较麻烦。

上面的几种方法都配合了 Python 脚本,当然你可以换成自己熟悉的语言,比如 Java,或者直接写 bash 脚本也可以。

脚本已经放到了 github 上,需要的同学请自取。地址:

点击获取源码


壮士且慢,先给点个赞吧,总是被白嫖,身体吃不消!

公众号「古时的风筝」,Java 开发者,全栈工程师,bug 杀手,擅长解决问题。

一个兼具深度与广度的程序员鼓励师,本打算写诗却写起了代码的田园码农!坚持原创干货输出,你可选择现在就关注我,或者看看历史文章再关注也不迟。长按二维码关注,跟我一起变优秀!

教你几招,快速创建 MySQL 五百万级数据,愉快的学习各种优化技巧的更多相关文章

  1. JDBC实现往MySQL插入百万级数据

    想往某个表中插入几百万条数据做下测试, 原先的想法,直接写个循环10W次随便插入点数据试试吧,好吧,我真的很天真.... DROP PROCEDURE IF EXISTS proc_initData; ...

  2. 利用JDBC或者事物或者调用存储过程实现往MySQL插入百万级数据

    转自:http://www.cnblogs.com/fnz0/p/5713102.html 想往某个表中插入几百万条数据做下测试, 原先的想法,直接写个循环10W次随便插入点数据试试吧,好吧,我真的很 ...

  3. 在Oracle中快速创建一张百万级别的表,一张十万级别的表 并修改两表中1%的数据 全部运行时间66秒

    万以下小表做性能优化没有多大意义,因此我需要创建大表: 创建大表有三种方法,一种是insert into table selec..connect by.的方式,它最快但是数据要么是连续值,要么是随机 ...

  4. mysql如何处理亿级数据,第一个阶段——优化SQL语句

    1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉 ...

  5. 百万级数据 MySQL处理(转)

    转自 http://www.cnblogs.com/win7xt/p/3156334.html 使用MySQL处理百万级以上数据时,不得不知道的几个常识   最近一段时间参与的项目要操作百万级数据量的 ...

  6. MYSQL百万级数据,如何优化

    MYSQL百万级数据,如何优化     首先,数据量大的时候,应尽量避免全表扫描,应考虑在 where 及 order by 涉及的列上建立索引,建索引可以大大加快数据的检索速度.但是,有些情况索引是 ...

  7. 一分钟在云端快速创建MySQL数据库实例

    本教程将帮助您了解如何使用Azure管理门户迅速创建,连接,配置MySQL 数据库 on Azure.完成本教程后,您将在Azure上拥有一个示例MySQL数据库服务器,并了解如何使用管理门户执行基本 ...

  8. mysql生成百万级数量测试数据

    今天因为项目需要,想测试一下读取百万级数量数据的速度如何,无奈数据库没有现成符合要求的数据,网上百度一番有很都不错的文章,但是需要涉及到一些存储过程和用php代码什么的,虽说可以实现,但是感觉另外再弄 ...

  9. 【转】使用MySQL处理百万级以上数据时,不得不知道的几个常识

    ---------------------------------------------------------------------------------------------------- ...

随机推荐

  1. 数据可视化之分析篇(九)PowerBI数据分析实践第三弹 | 趋势分析法

    https://zhuanlan.zhihu.com/p/133484654 以财务报表分析为例,介绍通用的分析方法论,整体架构如下图所示: (点击查看大图) 我会围绕这五种不同的方法论,逐步阐述他们 ...

  2. Python Ethical Hacking - ARP Spoofing

    Typical Network ARP Spoofing Why ARP Spoofing is possible: 1. Clients accept responses even if they ...

  3. P3379 最近公共祖先(LCA) 洛谷

    题意简单明了(这就是个模板). 就是让我们找2个节点的公共祖先而已,但我们要讲的做法不是生硬的爆搜,而且直接搜好像过不去…… 这次就讲我往后拖了n多天才开始学了倍增LCA. 嗯,这个题,如果2个节点的 ...

  4. C++ 深搜调错

    因为前两天某网站的比赛一个深搜错了,我只得了3等奖,我找不到错误,给别的大佬看他们又嫌恶心.emm……,比赛结束后我自己反思了一下,深搜写错了该怎么办,或者说怎样避免写错. 首先,变量名不要太ex,比 ...

  5. 3c数码商城

    目标:2020样卷 已完成:增删改查 未完成:有些小知识点不在意丢失,因此导致有些未完善 解决方案:写代码时不要走心,专心一点,减少失误,减少时间,增加效率,使自己的项目看起来更优秀,注意小知识的掌握 ...

  6. CentOS7编译安装php7.1配置教程详解

    这篇文章主要介绍CentOS7编译安装php7.1的过程和配置详解,亲测 ,需要的朋友可以参考. 1.首先安装依赖包: yum install libxml2 libxml2-devel openss ...

  7. DJANGO-天天生鲜项目从0到1-014-订单-订单评论

    本项目基于B站UP主‘神奇的老黄’的教学视频‘天天生鲜Django项目’,视频讲的非常好,推荐新手观看学习 https://www.bilibili.com/video/BV1vt41147K8?p= ...

  8. 记一次抓包和破解App接口

    目录 第一章 · 起源 第二章 · 尝试 第三章 · 脱狱 第四章 · 柳暗花明 第五章 · 终结 第一章 · 起源 某日,想做个爬虫工具,爬某个网站上的数据已做实验之用.大家都知道爬pc网页上的数据 ...

  9. 容器centos7安装部署ansible

    容器centos7安装部署ansible centos镜像版本及ansible版本 centos:centos7.5.1804 ansible:2.9.11 启动容器并进入容器 docker run ...

  10. Fortify Audit Workbench 笔记 Race Condition: Singleton Member Field 竞争条件:单例的成员字段

    Race Condition: Singleton Member Field 竞争条件:单例的成员字段 Abstract Servlet 成员字段可能允许一个用户查看其他用户的数据. Explanat ...