[英]Improving MySQLdb load data infile performance
我有一个在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中,我向一个DataFrame填充了500,000行(这可能不是最有效的方法,但是可以作为数据看起来像的一个示例):
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秒吗?
一些注意事项:
set sql_log_bin=0;
因为我没有数据库SUPER
权限 我已经进行了三项更改,并且我并没有停止衡量每次更改之间的效果,因此我不能100%确定每项更改的确切影响,但是我可以肯定地知道影响最大的是什么。
查看我的脚本的运行方式,您可以看到我批量插入的所有500k行的time
值完全相同:
# Append current time to the DataFrame
df["time"] = time.time
通过将time
放在主键的最左列,意味着我要插入的所有行都将聚集在一起,而不必在表中进行拆分。
当然,这样做的问题是它使索引对我最常见的查询无用:返回给定key1
, key2
和key3
组合的所有“时间”(例如: SELECT * FROM my_table WHERE key1 = ... AND key2 = ... AND key3 = ...
)
为了解决这个问题,我必须添加另一个密钥:
PRIMARY KEY (`time`, `key1`, `key2`, `key3`),
KEY (`key1`, `key2`, `key3`)
我调整了表,使列的顺序与主键的顺序( time
, key1
, key2
, key3
)匹配
我不知道这是否有效果,但可能有
我在DataFrame上运行了以下命令:
df.reindex(columns=["value1", "value2"], inplace=True)
这对列进行了排序,以匹配它们在数据库中出现的顺序。 在此更改与更改2之间,可以按原样导入行,而无需交换列的顺序。 我不知道这是否会对进口业绩产生影响
通过这三个更改,我的导入时间从2分钟降低到了9秒! 绝对不可思议
我担心向表中添加额外的键,因为额外的索引意味着更长的写入时间和更多的磁盘空间,但是效果几乎可以忽略不计-尤其是与正确地对键进行群集所节省的成本相比。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.