与从 CSV 文件导出和导入相比,Python MySQLdb SScursor 速度较慢。可以加速吗?

Posted

技术标签:

【中文标题】与从 CSV 文件导出和导入相比,Python MySQLdb SScursor 速度较慢。可以加速吗?【英文标题】:Python MySQLdb SScursor slow in comparison to exporting and importing from CSV-file. Speedup possible? 【发布时间】:2013-07-07 09:11:54 【问题描述】:

作为构建数据仓库的一部分,我必须在源数据库表中查询大约 7500 万行。

我想要对 75M 行做一些处理,然后将结果添加到另一个数据库中。现在,这是相当多的数据,我主要通过两种方法取得了成功:

1) 使用 mysql 的“SELECT ... INTO”功能将查询导出到 CSV 文件,并使用 python 的 fileinput 模块读取它,并且

2) 使用 MySQLdb 的 SScursor 连接到 MySQL 数据库(默认游标将查询放在内存中,杀死 python 脚本)并以大约 10k 行的块获取结果(这是我发现的块大小最快)。

第一种方法是“手动”执行 SQL 查询(大约需要 6 分钟),然后使用 Python 脚本读取 csv 文件并对其进行处理。我使用 fileinput 读取文件的原因是 fileinput 不会从一开始就将整个文件加载到内存中,并且适用于较大的文件。仅遍历文件(读取文件中的每一行并调用 pass)大约需要 80 秒,即 1M 行/秒。

第二种方法是一个 python 脚本执行相同的查询(也需要大约 6 分钟,或者稍长一些),然后只要 SScursor 中还有任何剩余的行,就会执行一个 while 循环来获取行块。在这里,仅读取行(一个接一个地获取一个块而不做任何其他事情)大约需要 15 分钟,或大约 85k 行/秒。

上面的两个数字 (rows/s) 可能没有真正的可比性,但是在我的应用程序中对这两种方法进行基准测试时,第一个大约需要 20 分钟(其中大约 5 分钟是 MySQL 转储到 CSV 文件),并且第二个大约需要 35 分钟(其中大约 5 分钟是正在执行的查询)。这意味着转储和读取 CSV 文件的速度大约是直接使用 SScursor 的两倍。

如果它不限制我系统的可移植性,这将没有问题:“SELECT ... INTO”语句要求 MySQL 具有写入权限,我怀疑这不如使用游标安全。另一方面,15 分钟(并且随着源数据库的增长而增长)并不是我在每次构建时都可以节省的时间。

那么,我错过了什么吗? SScursor 是否有任何已知的原因比转储/读取到/从 CSV 文件慢得多,这样文件输入在 SScursor 不是的地方进行了 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)

最后,我在 Ubuntu 13.04 上使用 MySQL 服务器 5.5.31。我使用 Python 2.7.4 和 MySQLdb 1.2.3。谢谢你陪我这么久!

【问题讨论】:

由于您没有代表来回答您自己的问题,我已将您问题中看起来更像是解决方案的部分移到了社区 wiki 答案中。如果您有更多改进要分享和/或接受它(如果它或多或少完整),您可以编辑此 wiki 答案。 【参考方案1】:

使用cProfile 后,我发现很多时间都花在隐式构造 Decimal 对象上,因为这是从 SQL 查询返回到我的 Python 脚本中的数字类型。在第一种方法中,十进制值作为整数写入 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 中重写查询以减少函数调用可以显着提高整体处理速度。

【讨论】:

以上是关于与从 CSV 文件导出和导入相比,Python MySQLdb SScursor 速度较慢。可以加速吗?的主要内容,如果未能解决你的问题,请参考以下文章

mysql导入导出csv文件

将导出的 grafana csv 文件导入 python pandas 有意义吗?

mongodb导入csv文件时出错

python 从KeepassX转换导出的XML文件,可以将其导入1Password(通过CSV逗号分隔文件)

Python+Pandas入门2——导出csv

PHP如何导出导入CSV文件?