改善MySQLdb加载数据的性能

时间:2018-03-16 18:26:40

标签: python pandas innodb mysql-python

我有一张表在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权限

1 个答案:

答案 0 :(得分: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秒! 这绝对令人难以置信

我担心将额外的密钥添加到表中,因为额外的索引意味着更长的写入时间和更多的磁盘空间,但效果几乎可以忽略不计 - 特别是与正确聚类我的密钥所带来的巨大节省相比。