提高 MySQLdb 加载数据 infile 性能

Posted

技术标签:

【中文标题】提高 MySQLdb 加载数据 infile 性能【英文标题】:Improving MySQLdb load data infile performance 【发布时间】:2018-08-25 21:52:39 【问题描述】:

我有一个在 InnoDB 中大致定义如下的表:

create table `my_table` (
  `time` int(10) unsigned not null,
  `key1` int(10) unsigned not null,
  `key3` char(3) unsigned not null,
  `key2` char(2) unsigned not null,
  `value1` float default null,
  `value2` float default null,
  primary key (`key1`, `key2`, `key3`, `time`),
  key (`key3`, `key2`, `key1`, `time`)
) engine=InnoDB default character set ascii
partition by range(time) (
  partition start        values less than (0),
  partition from20180101 values less than (unix_timestamp('2018-02-01')),
  partition from20180201 values less than (unix_timestamp('2018-03-01')),
  ...,
  partition future       values less than MAX_VALUE
)

是的,列顺序与键顺序不匹配。

在 Python 中,我使用 500,000 行填充了一个 DataFrame(这可能不是最有效的方法,但可以作为数据看起来的示例):

import random
import pandas as pd
key2_values = ["aaa", "bbb", ..., "ttt"]  # 20 distinct values
key3_values = ["aa", "ab", "ac", ..., "az", "bb", "bc", ..., "by"]  # 50 distinct values
df = pd.DataFrame([], columns=["key1", "key2", "key3", "value2", "value1"])
idx = 0
for x in range(0, 500):
    for y in range(0, 20):
        for z in range(0, 50):
            df.loc[idx] = [x, key2_values[y], key3_values[z], random.random(), random.random()]
            idx += 1
df.set_index(["key1", "key2", "key3"], inplace=True)

(实际上,这个 DataFrame 是由多个 API 调用和大量数学运算填充的,但最终结果是相同的:一个巨大的 DataFrame,其中包含约 500,000 行和与 InnoDB 表匹配的键)

要将此 DataFrame 导入表中,我目前正在执行以下操作:

import time
import mysqldb
conn = MySQLdb.connect(local_infile=1, **connection_params)
cur = conn.cursor()
# Disable data integrity checks -- I know the data is good
cur.execute("SET foreign_key_checks=0;")
cur.execute("SET unique_checks=0;")
# Append current time to the DataFrame
df["time"] = time.time()
df.set_index(["time"], append=True, inplace=True)
# Sort data in primary key order
df.sort_index(inplace=True)
# Dump the data to a CSV
with open("dump.csv", "w") as csv:
    df.to_csv(csv)
# Load the data
cur.execute(
    """
        load data local infile 'dump.csv'
        into table `my_table`
        fields terminated by ','
        enclosed by '"'
        lines terminated by '\n'
        ignore 1 lines
        (`key1`, `key2`, `key3`, `time`, `value`)
    """
)
# Clean up
cur.execute("SET foreign_key_checks=1;")
cur.execute("SET unique_checks=1;")
conn.commit()

在这方面的所有表现都还不错。我可以在大约 2 分钟内导入 500,000 行。如果可能的话,我想更快地做到这一点。

我是否缺少任何技巧或可以进行任何更改以将其缩短到 30-45 秒?

一些注意事项:

不知道对DataFrame中的重新排序是否会影响性能。目前DataFrame中的列顺序与数据库不匹配 不知道改变数据库中列的顺序来匹配主键的顺序是否会影响性能(目前“时间”是第一位的,虽然它是索引的第四个键) 更改数据库配置可能很困难,因为我无法直接访问数据库服务器。我坚持使用已经存在的任何硬件和配置选项。任何性能改进都必须来自我的 Python 代码 我可以更改表定义(包括更改分区),但我希望尽可能避免这种情况,因为已经有大量历史数据并将其复制到另一个表需要很久。丢失这些数据是一种选择,但我宁愿避免 我无法使用set sql_log_bin=0;,因为我没有数据库的SUPER 权限

【问题讨论】:

既然你没有SUPER权限,这是RDS实例吗?如果是这样,EBS 卷大小是多少?如果它很小,默认情况下您可能没有足够的 IOPS。在我的 Macbook 上,我可以使用 LOAD DATA INFILE 每秒获得​​约 50,000 行。请参阅我的演示文稿Load Data Fast! 它实际上是由我公司的 DBA 管理的内部 MariaDB 服务器。从技术上讲,我可以提交一张 Jira 票,要求他们调整设置,但需要 2-3 周才能得到回复,而且回复通常是否定的。不幸的是,我不知道服务器的详细信息(磁盘大小、磁盘 I/O 等) 听起来它运行在性能非常低的硬件上,或者可能已经承受了高负载。无论哪种方式,这都不是可以在 Stack Overflow 上解决的问题。 Python 代码没有任何变化可以使 LOAD DATA INFILE 运行得比数据库服务器运行得更快。 严格来说并非如此。以主键顺序加载数据提高了LOAD DATA INFILE 的性能,SET unique_checks=0; 也是如此。我现在正在执行测试,看看是否调整键以使 time 是最左边的主键会进一步提高性能(因为插入的所有行共享一次) 你在写一个 500K 行的 csv 吗?还是一些更小的块? 2 分钟中有多少时间在构建 csv;负载数据是多少? 【参考方案1】:

我进行了三项更改,并且没有停下来衡量每次更改之间的性能,因此我无法100%确定每个更改的确切影响,但我可以合理地我当然知道影响更大的是什么。

更改 1(可以肯定这影响最大)- 修改主键

查看我的脚本的运行方式,您可以看到我批量插入的所有 500k 行都具有完全相同的 time 值:

# Append current time to the DataFrame
df["time"] = time.time

通过将time 设为主键的最左侧列意味着我插入的所有行都将聚集在一起,而不必将它们拆分到整个表中。

当然,问题在于它使索引对我最常见的查询毫无用处:返回给定 key1key2key3 组合的所有“时间”(例如:SELECT * FROM my_table WHERE key1 = ... AND key2 = ... AND key3 = ...

为了解决这个问题,我不得不添加另一个键:

PRIMARY KEY (`time`, `key1`, `key2`, `key3`),
KEY (`key1`, `key2`, `key3`)

变更2(可能有影响)——修改列顺序

我调整了表格,使列的顺序与主键的顺序相匹配(timekey1key2key3

我不知道这是否有影响,但它可能有

更改 3(可能有影响)--调整了 CSV 中的列顺序

我在我的 DataFrame 上运行了以下内容:

df.reindex(columns=["value1", "value2"], inplace=True)

这对列进行排序以匹配它们在数据库中出现的顺序。在此和更改 2 之间,可以完全按原样导入行,而无需交换列的顺序。不知道对导入性能有没有影响

结果

通过这三个更改,我的导入时间从 2 分钟缩短到 9 秒! 太不可思议了

我担心向表中添加额外的键,因为额外的索引意味着更长的写入时间和更多的磁盘空间,但效果几乎可以忽略不计 - 特别是与我通过正确集群我的键所节省的大量成本相比。

【讨论】:

干得好!我原以为主键顺序会更好,但不会那么好。 我会注意到,重新排列主键极大地提高了我的写入性能,但严重损害了我的读取性能。过去运行我的常见查询需要 2-3 秒(返回给定键集的所有“时间”)。现在运行相同的查询需要 30 秒。虽然我不喜欢这样,但我们用这个数据库写的频率比读的多,所以这是可以接受的。 @stevendesu - 将time 添加到用于该SELECT 的索引上;这将使它成为一个“覆盖”索引,因此速度更快。 @RickJames 我将测试将time 添加到用于SELECT 的索引中,但我怀疑我会看到巨大的性能提升。我已经能够非常快速地运行我的SELECT 语句。如果有很大的改进,我一定会通知您。

以上是关于提高 MySQLdb 加载数据 infile 性能的主要内容,如果未能解决你的问题,请参考以下文章

加载数据 infile 啥都不做

Infobright/MySQL 加载数据 infile 死锁

如何保护加载数据本地infile更新查询免受sql注入

MySQL加载数据infile - 加速?

mysql 5.1.73 加载数据本地infile错误

mysql加载数据infile where子句