我正在开发一个使用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源代码,看起来它不应该做任何事情。
答案 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