Почему slowmany замедляется в Python MySQLdb?
Я разрабатываю программу на Python, которая обращается к базе данных MySQL, используя MySQLdb. В некоторых ситуациях мне нужно запустить команду 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, и похоже, что он ничего не должен делать.
Ответы
Ответ 1
Попробуйте уменьшить слово "значения" в запросе - это, по-видимому, ошибка/регрессия в MySQL-python 1.2.3.
Выполнение MySQL-python executeemany() соответствует предложению VALUES с регулярным выражением, а затем просто клонирует список значений для каждой строки данных, поэтому вы в конечном итоге выполняете точно такой же запрос, как и ваш первый подход.
К сожалению, регулярное выражение потеряло флагом без учета регистра в этой версии (впоследствии исправлено в trunk r622, но никогда не было обращено в ветвь 1.2), поэтому он ухудшает итерирование данных и отключение запроса в строке.
Ответ 2
Ваш первый пример - это один (большой) оператор, который сгенерирован и затем отправляется в базу данных.
Второй пример - гораздо более простой оператор, который вставляет/заменяет одну строку, но выполняется несколько раз. Каждая команда отправляется в базу данных отдельно, поэтому вы должны оплачивать время обработки с клиента на сервер и обратно для каждой вставленной строки. Я думаю, что эта дополнительная латентность, введенная между командами, является основной причиной снижения производительности второго примера.
Ответ 3
Не рекомендуется использовать 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))
Текущая реализация очень простая и надежная.