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

文章会收录在 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. Ethical Hacking - GAINING ACCESS(18)

    CLIENT SIDE ATTACKS Backdooring ANY file Combine backdoor with any file - Generic solution. Users ar ...

  2. Python Ethical Hacking - VULNERABILITY SCANNER(6)

    EXPLOITATION - XSS VULNS EXPLOITING XSS Run any javascript code. Beef framework can be used to hook ...

  3. JS 原型与原型链终极详解(二)

    四. __proto__ JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象. 对象 person1 有一个 __pr ...

  4. 使用Thanos实现Prometheus指标联邦

    本文来自Rancher Labs Prometheus是CNCF中已经毕业的项目之一,主要用于监控和告警.在Kubernetes生态中,它是应用最为广泛的监控和告警工具之一.Rancher用户可以通过 ...

  5. xilinx fpga 生成3*3窗口

    在写滤波程序的时候在网上看了好几篇大佬的笔记,都有提到使用3*3窗口,由于小白一个,看到复杂的理论就惧怕的不行.但是现在不得不上,自己调用移位寄存器ip核然后做了个3*3窗口出来,自己动手作出来忽然感 ...

  6. 利用python实现平稳时间序列的建模方式

    一.平稳序列建模步骤 假如某个观察值序列通过序列预处理可以判定为平稳非白噪声序列,就可以利用ARMA模型对该序列进行建模.建模的基本步骤如下: (1)求出该观察值序列的样本自相关系数(ACF)和样本偏 ...

  7. Java线程的6种状态及切换

    Java中线程的状态分为6种. 1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法.2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running) ...

  8. web自动化 -- HTMLreport(二)测试报告输出内容居左对齐

    一.需求痛点 1.报告输出内容是居中对齐,很难看 2.我们希望输出内容是居左对齐 3.痛点截图 二.解决办法 1.原因分析 HTMLreport的内容对齐方式,自然是修改HTMLreport的内容了 ...

  9. netty 使用字典提升短文本的压缩效果

    1 问题 术语:压缩率,compression ratio,压缩后的大小/压缩前的大小,越小说明压缩效果越好. 在使用netty的JdkZlibEncoder进行压缩时,发现了一个问题:它对于短文本( ...

  10. BUUCTF-web NiZhuanSiWei

    上源码: file_get_contents读文件,text参数用php://input发送字符串.file参数值为被包含文件的文件名 成功执行第一个if,紧接着用php://filter/read= ...