作为构建数据仓库的一部分,我必须在源数据库表中查询大约75M行。
我想对75M行做些什么是处理,然后将结果添加到另一个数据库中。现在,这是非常多的数据,我主要采用两种方法取得了成功:
1)使用MySQL的“SELECT ... INTO”功能将查询导出到CSV文件,并使用python的fileinput模块读取它,
2)使用MySQLdb的SScursor连接到MySQL数据库(默认光标将查询放入内存,查杀python脚本)并以大约10k行的块(这是我发现的块大小)获取结果是最快的。
第一种方法是“手动”执行的SQL查询(大约需要6分钟),然后是读取csv文件并处理它的python脚本。我使用fileinput读取文件的原因是fileinput从一开始就没有将整个文件加载到内存中,并且适用于较大的文件。只需遍历文件(读取文件中的每一行并调用pass)大约需要80秒,即1M行/秒。
第二种方法是执行相同查询的python脚本(也需要大约6分钟,或稍长一些),然后只要在SScursor中有任何剩余部分,就可以循环获取行的块。在这里,只需读取行(一个接一个地取出一个块而不做其他任何操作)大约需要15分钟,或大约85k行/秒。
上面的两个数字(行/秒)可能无法真正比较,但在我的应用程序中对这两种方法进行基准测试时,第一个需要大约20分钟(其中大约五个是MySQL转储到CSV文件中),以及第二个需要大约35分钟(其中大约五分钟是正在执行的查询)。这意味着对CSV文件的转储和读取速度大约是直接使用SScursor的两倍。
如果它不限制我的系统的可移植性,这将没有问题:“SELECT ... INTO”语句要求MySQL具有写入权限,我怀疑它不像使用游标那样安全。另一方面,15分钟(并且随着源数据库的增长而增长)并不是我在每次构建时都可以节省的。
那么,我错过了什么吗?是否有任何已知的原因使SScursor比转储/读取/从CSV文件慢得多,这样在没有SScursor的情况下,fileinput是C优化的?有关如何处理此问题的任何想法?有什么要测试的?我相信SScursor可能和第一种方法一样快,但在阅读了所有我能找到的事情后,我很难过。
现在,代码:
并不是说我认为查询存在任何问题(它的速度和我要求的一样快,并且在两种方法中都花费相同的时间),但这是为了完整性:
SELECT LT.SomeID, LT.weekID, W.monday, GREATEST(LT.attr1, LT.attr2)
FROM LargeTable LT JOIN Week W ON LT.weekID = W.ID
ORDER BY LT.someID ASC, LT.weekID ASC;
第一种方法中的主要代码是这样的
import fileinput
INPUT_PATH = 'path/to/csv/dump/dump.csv'
event_list = []
ID = -1
for line in fileinput.input([INPUT_PATH]):
split_line = line.split(';')
if split_line[0] == ID:
event_list.append(split_line[1:])
else:
process_function(ID,event_list)
event_list = [ split_line[1:] ]
ID = split_line[0]
process_function(ID,event_list)
第二种方法的主要代码是:
import MySQLdb
...opening connection, defining SScursor called ssc...
CHUNK_SIZE = 100000
query_stmt = """SELECT LT.SomeID, LT.weekID, W.monday,
GREATEST(LT.attr1, LT.attr2)
FROM LargeTable LT JOIN Week W ON LT.weekID = W.ID
ORDER BY LT.someID ASC, LT.weekID ASC"""
ssc.execute(query_stmt)
event_list = []
ID = -1
data_chunk = ssc.fetchmany(CHUNK_SIZE)
while data_chunk:
for row in data_chunk:
if row[0] == ID:
event_list.append([ row[1], row[2], row[3] ])
else:
process_function(ID,event_list)
event_list = [[ row[1], row[2], row[3] ]]
ID = row[0]
data_chunk = ssc.fetchmany(CHUNK_SIZE)
process_function(ID,event_list)
最后,我使用的是MySQL服务器5.5.31的Ubuntu 13.04。我使用Python 2.7.4和MySQLdb 1.2.3。谢谢你这么久和我在一起!
答案 0 :(得分:2)
使用cProfile
后,我发现花了大量时间隐式构造Decimal对象,因为那是从SQL查询返回到我的Python脚本的数字类型。在第一种方法中,Decimal值作为整数写入CSV文件,然后由Python脚本读取。 CSV文件I / O“扁平化”数据,使脚本更快。这两个脚本现在的速度大致相同(第二种方法仍然只是稍微慢一些)。
我还将MySQL数据库中的日期转换为整数类型。我的查询现在是:
SELECT LT.SomeID,
LT.weekID,
CAST(DATE_FORMAT(W.monday,'%Y%m%d') AS UNSIGNED),
CAST(GREATEST(LT.attr1, LT.attr2) AS UNSIGNED)
FROM LargeTable LT JOIN Week W ON LT.weekID = W.ID
ORDER BY LT.someID ASC, LT.weekID ASC;
这几乎消除了两种方法之间处理时间的差异。
这里的教训是,在进行大型查询时,数据类型的后期处理很重要!在Python中重写查询以减少函数调用可以显着提高整体处理速度。