为什么Python MySQLdb中的executemany速度慢?

时间:2010-10-15 19:55:27

标签: python sql mysql performance

我正在开发一个使用MySQLdb访问MySQL数据库的Python程序。在某些情况下,我必须在许多行上运行INSERT或REPLACE命令。我目前正在这样做:

db.execute("REPLACE INTO " + table + " (" + ",".join(cols) + ") VALUES" +
    ",".join(["(" + ",".join(["%s"] * len(cols)) + ")"] * len(data)),
    [row[col] for row in data for col in cols])

它工作正常,但有点尴尬。我想知道我是否可以让它更容易阅读,我发现了executemany命令。我将代码更改为:

db.executemany("REPLACE INTO " + table + " (" + ",".join(cols) + ") " + 
    "VALUES(" + ",".join(["%s"] * len(cols)) + ")",
    [tuple(row[col] for col in cols) for row in data])

它仍然有效,但运行速度慢了很多。在我的测试中,对于相对较小的数据集(大约100-200行),它运行速度慢了约6倍。对于大数据集(大约13,000行,我期望处理的最大行),它运行速度慢了约50倍。为什么要这样做?

我真的想简化我的代码,但我不希望性能大幅下降。有谁知道如何让它更快?

我正在使用Python 2.7和MySQLdb 1.2.3。我尝试修改setinputsizes函数,但似乎没有做任何事情。我查看了MySQLdb源代码,看起来它不应该做任何事情。

4 个答案:

答案 0 :(得分:20)

尝试在查询中小写“值”这个词 - 这似乎是MySQL-python 1.2.3中的错误/回归。

MySQL-python的executemany()实现将VALUES子句与正则表达式匹配,然后只是克隆每行数据的值列表,因此最终执行与第一种方法完全相同的查询。

不幸的是,正则表达式在该版本中丢失了它不区分大小写的标志(随后在trunk r622中修复但从未向后移植到1.2分支)因此它会降级为迭代数据并触发每行查询。

答案 1 :(得分:1)

您的第一个示例是生成的单个(大)语句,然后发送到数据库。

第二个例子是一个更简单的语句,它插入/替换单行但多次执行。每个命令都单独发送到数据库,因此您必须支付从客户端到服务器的周转时间,并为插入的每一行返回。我认为命令之间引入的额外延迟是第二个例子性能下降的主要原因。

答案 2 :(得分:1)

强烈建议不要在executeMany中使用pyodbc以及ceodbc这两者都很慢并且包含很多错误。

相反,请考虑使用execute并使用简单的字符串格式手动构建SQL查询。

transaction = "TRANSACTION BEGIN {0} COMMIT TRANSACTION

bulkRequest = ""
for i in range(0, 100)
    bulkRequest = bulkRequest + "INSERT INTO ...... {0} {1} {2}"

ceodbc.execute(transaction.format(bulkRequest))

目前的实施非常简单快速可靠。

答案 3 :(得分:0)

如果您使用的是mysqlclient-python(MySQLdb1的叉子),也是Django推荐的驱动程序(由Django推荐),则需要了解以下用例:

如果查询的形式为

cursor.executemany,则可以(静默地)使用cursor.execute:

  

INSERT INTO testdb.test (type, some_field, status, some_char_field) VALUES (%s, hex(%s), %s, md5(%s));

驱动程序使用的python正则表达式似乎不支持在VALUES子句中使用mysql函数。

    RE_INSERT_VALUES = re.compile(
    r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)" +
    r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" +
    r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z",
    re.IGNORECASE | re.DOTALL)

链接到相关的github问题https://github.com/PyMySQL/mysqlclient-python/issues/334