测试基础 生成 100w 条数据,哪种方式最快

花菜 · 2023年07月27日 · 最后由 花菜 回复于 2023年08月01日 · 8535 次阅读

1、背景

在技术群里面有个讨论,枚举字段是否需要增加索引的问题

有人说必须要,有人说加了索引没什么用。

talk is cheap, show me the data.
讨论那么多,还是得先有数据来验证。

说到这里,你可以先猜猜哪种最快,是储存过程最快的吗?

2、建表语句

数据库字段使用字符串代替枚举,由程序控制字段的值

create table users
(
    id     int auto_increment
        primary key,
    name   varchar(255) not null,
    age    int          not null,
    gender varchar(10)  not null
);
# 3、插入数据对比
## 3.1 存储过程
```sql
CREATE PROCEDURE GenerateData()
             BEGIN
               DECLARE i INT DEFAULT 1;

               WHILE i <= 1000000 DO
                 INSERT INTO users (name, age, gender)
                 VALUES (
                   CONCAT('User', i),
                   FLOOR(18 + RAND() * (100 - 18)),
                   CASE
                     WHEN i % 3 = 1 THEN 'male'
                     WHEN i % 3 = 2 THEN 'female'
                     ELSE 'unknown'
                   END
                 );
                 SET i = i + 1;
               END WHILE;

             END
 CALL GenerateData();

3.2 python 生成 insert 语句批量提交

import pymysql
import random

# 连接数据库
conn = pymysql.connect(host='localhost',port=3306, user='root', password='root', db='my_database', charset='utf8mb4')
cur = conn.cursor()

try:
    # 开启事务
    conn.begin()

    for i in range(1, 1000001):
        name = f'User{i}'
        age = random.randint(18, 100)
        gender = 'male' if i % 3 == 1 else 'female' if i % 3 == 2 else 'unknown'

        # 执行插入语句
        cur.execute("INSERT INTO users (name, age, gender) VALUES (%s, %s, %s)", (name, age, gender))

    # 提交事务
    conn.commit()
except Exception as e:
    print(f"An error occurred: {e}")
    # 发生错误时回滚
    conn.rollback()
finally:
    # 关闭游标和连接
    cur.close()
    conn.close()

3.3 使用文件导入

3.3.1 生成 csv 文件

import csv
import random

# 生成包含1,000,000条用户记录的CSV文件
with open('users.csv', 'w', newline='') as file:
    writer = csv.writer(file)
    for i in range(1, 1000001):
        name = f'User{i}'
        age = random.randint(18, 100)
        gender = 'male' if i % 3 == 1 else 'female' if i % 3 == 2 else 'unknown'
        writer.writerow([name, age, gender])

3.3.2 导入文件

# 数据库需要能读取到这个路径,否则会报错
# [2023-07-26 23:08:28] [HY000][13] (conn=151) Can't get stat of '/tmp/users.csv' (Errcode: 2 "No such file or directory")
# 如果使用的容器,需要把宿主机上的文件拷贝到容器
# 下面的例子是把宿主机/local/users.csv 拷贝到容器的根目录/
# docker cp /local/users.csv db-docker-compose-db-1:/


LOAD DATA INFILE '/users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(name, age, gender);

如果数据库不在本地,不方便拷贝。可以使用 IDEA 自带的 database 导入功能

image.png

3.4 从已有的表中拷贝

INSERT INTO user_has_index SELECT * FROM users

4、耗时对比

4.1 文件导入

  • 所有字段均无索引
    没有索引

  • gender 字段有索引
    有索引

4.2 从已有的表中拷贝

image.png

4.3 储存过程

太慢啦,我手动中断,此时有57w左右的数据

4.4 python 批量插入

python 批量插入(没计算时间,等了也有几分钟,很慢)

5、总结

  • 存储过程并不是最快的,甚至很慢
  • 文件导入是最快的
  • 使用 Python 直接批量 insert 不可取

你猜对了吗?

或者还有更快的方案,比如说分批 + 事务。

欢迎留言和我交流。

公众号原文

共收到 24 条回复 时间 点赞

存储过程是 gpt4 写的,效率上可能存在一定问题,有懂的大佬,欢迎指正

图挂了

恒温 回复

我用手机打开没问题哦,可能是 CloudFlare 的 R2 图床对部分地区不友好😭

以下回答来自 gpt3.5:
插入速度的快慢取决于多个因素,包括硬件性能、数据库引擎、网络延迟等。然而,根据常见的情况和经验来看,一般来说,插入速度从快到慢的顺序可能是:

文件导入:将数据存储在文件中,然后使用数据库提供的导入工具将文件导入到数据库中。这种方法通常是最快的,因为它直接将数据加载到数据库中,避免了一些额外的开销。
Python 批量插入:使用 Python 编写脚本,通过数据库连接库(如 MySQLdb、psycopg2 等)批量插入数据。这种方法通常比存储过程更快,因为 Python 可以通过批量操作减少与数据库的交互次数。
存储过程:使用数据库提供的存储过程功能,将数据逐条插入到数据库中。这种方法通常是最慢的,因为每次插入都需要与数据库进行交互,增加了额外的开销。
需要注意的是,以上只是一种常见的情况,具体的插入速度还受到其他因素的影响,如数据量的大小、数据库的配置等。在实际应用中,可以根据具体情况选择最适合的方法。

这是不是意味着在生成百万级数据时,生成的数据放到文件中上传更好一点;如果更大量级的话,文件的读取可能会存在瓶颈,就需要使用其他办法了

Violet_yoo 回复

读取文件不会是瓶颈,频繁的网络 io 才是瓶颈;
后面测试了 Python 分批提交,效果会好很多

花菜 回复

很不友好,下次可以考虑换一个😋

花菜 #17 · 2023年07月27日 Author
小叮当 回复

白嫖的,只能将就啦

很显然文件导入的方式,mysql 自动用了并行处理,默认线程数我猜应该是用 innodb_write_io_threads 这个参数的值,你 load data 的时候可以试试--user-threads=1 和 16 分别试试效率咋样
procedure 和外部程序,不管 python 还是 go,你自己用单线程循环,怎么快得起来,你拆 100 线程试试

花菜 #15 · 2023年07月28日 Author
槽神 回复


这里是一个线程,但是用分批 + 批量的方式,效率会有大幅度提高

花菜 #11 · 2023年07月28日 Author
槽神 回复

innodb_write_io_threads 默认值是 4,但没有 load data,user-threads 这个参数哦,多客户端的话就太麻烦了。

花菜 回复

那应该是 mysqlimport 的参数了

花菜 回复

存储过程是最快的,原理可以搜索下。要把存储过程改成一次提交多条插入。

花菜 #14 · 2023年07月29日 Author
测试新人 回复

一次性插入多条是多少条呢?10000 条,用分批 + 事务,其实我试过,但并没有。
如果你有最快的,希望可以写出来,我想学习一波。

花菜 回复

bash shell 比较快些,代码也简单

花菜 #16 · 2023年07月30日 Author
etang 回复

给个例子?

花菜 回复

将 100W 行数据 ,使用 shell 拆分成 10 个文件,每个文件 10W 行,使用 shell for 循环,启动 10 个 mysql loaddata 命令,分别加载被拆分的 10 个文件。你可以测试下这样的场景导入效率

花菜 #18 · 2023年07月31日 Author
青团儿 回复

这样做就太啰嗦了,要这样,其实还不如用 Python10 个线程呢。py 一样能执行 mysql loaddata,最终都是转成 sql。

用 navicat 的用现成表生成字段,100w 行这么少的字段大概 10 几 s.
python...N 个线程还不如用 js 单线程。

花菜 #20 · 2023年07月31日 Author
陈子昂 回复

文章中没提到 navicat 呀。
好奇为啥会觉得 js 单线程更加快呢,瓶颈在 db 的 server 端呀

我估计使用 insert select 这种方式更快, 如果 postgresql 可以用类似下面的

insert into towns (
    code, article, name, department
)
select
    left(md5(i::text), 10),
    md5(random()::text),
    md5(random()::text),
    left(md5(random()::text), 4)
from generate_series(1, 1000000) s(i)
simonpatrick 回复

看 4.2 的结果,就是 insert into 呢

花菜 回复

insert select 跟你的 py 程序一个道理,如果你使用分批次提交,速度肯定会有所不同,毕竟 100w 数据结构化之后交给 mysql,一次性写入还是对 db server 配置有点要求和压力的,如果缓冲区/内存不足可能就会走 swap……balabala,function、procedure 同理,都可以分批,参考:https://www.yzktw.com.cn/post/932913.html

其实你这个比对本身给人的感觉就是,只给了结论,没有深入揭示背后的原因,像 dba 配置、插入方案差异、程序写法差异、不同数据量的差异……甚至最后可以扯皮扯到像 19# 陈大猫提到的不同语言、不同 jdbc 驱动的性能差异,不过没啥意义了,语言鄙视链毫无意义,毕竟业务场景决定一切

槽神 回复

当然,这篇文章存在很多的不足之处呢

我目前的需求,只需要知道哪个更快即可,文章开头也有说背景,在讨论索引的问题,需要造数。
至于背后原理,很欢迎对 MySQL 更加了解的大佬来补充。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册