Есть ли способ заставить SQLAlchemy выполнять массовую вставку вместо вставки каждого отдельного объекта. т.е.
делать:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
скорее, чем:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Я только что преобразовал некоторый код для использования sqlalchemy, а не raw sql, и хотя теперь работать с ним намного приятнее, теперь он кажется медленнее (до 10 раз), мне интересно, является ли это причиной.
Может быть, я смогу улучшить ситуацию с помощью сессий более эффективно. На данный момент у меня есть autoCommit=False
и делаю session.commit()
после того, как добавлю кое-что. Хотя это, кажется, приводит к тому, что данные становятся устаревшими, если БД изменена в другом месте, например, даже если я сделаю новый запрос, я все равно верну старые результаты?
Спасибо за вашу помощь!
Ответы:
SQLAlchemy представил это в версии
1.0.0
:Массовые операции - документация SQLAlchemy
С помощью этих операций теперь вы можете выполнять массовую вставку или обновление!
Например, вы можете:
Здесь будет сделана объемная вставка.
источник
\copy
помощью psql (от того же клиента на тот же сервер) я вижу огромную разницу в производительности на стороне сервера, что приводит к примерно в 10 раз большему количеству вставок / с. По-видимому, массовая загрузка с использованием\copy
(илиCOPY
на сервере) с использованием упаковки при обмене данными от клиента к серверу намного лучше, чем использование SQL через SQLAlchemy. Дополнительная информация: Большая объемная вставка разница в производительности PostgreSQL против ... .В документации sqlalchemy есть описание производительности различных методов, которые можно использовать для массовых вставок:
источник
Насколько мне известно, нет способа заставить ORM выполнять массовые вставки. Я считаю, что основная причина в том, что SQLAlchemy необходимо отслеживать идентичность каждого объекта (то есть новые первичные ключи), а массовая вставка мешает этому. Например, предположим, что ваша
foo
таблица содержитid
столбец и сопоставлена сFoo
классом:Поскольку SQLAlchemy получил значение для
x.id
без выдачи другого запроса, мы можем сделать вывод, что он получил значение непосредственно изINSERT
оператора. Если вам не нужен последующий доступ к созданным объектам через те же экземпляры, вы можете пропустить слой ORM для своей вставки:SQLAlchemy не может сопоставить эти новые строки с какими-либо существующими объектами, поэтому вам придется запрашивать их заново для любых последующих операций.
Что касается устаревших данных, полезно помнить, что сеанс не имеет встроенного способа узнать, когда база данных изменяется вне сеанса. Чтобы получить доступ к измененным извне данным через существующие экземпляры, экземпляры должны быть помечены как просроченные . По умолчанию это происходит
session.commit()
, но это можно сделать вручную, позвонивsession.expire_all()
илиsession.expire(instance)
. Пример (SQL опущен):session.commit()
истекаетx
, поэтому первый оператор печати неявно открывает новую транзакцию и повторно запрашиваетx
атрибуты. Если вы закомментируете первый оператор печати, вы заметите, что второй теперь принимает правильное значение, потому что новый запрос не создается до тех пор, пока не будет выполнено обновление.Это имеет смысл с точки зрения изоляции транзакций - вы должны учитывать только внешние модификации между транзакциями. Если это вызывает у вас проблемы, я бы посоветовал уточнить или переосмыслить границы транзакций вашего приложения вместо того, чтобы немедленно достигать их
session.expire_all()
.источник
autocommit=False
, я считаю , вы должны называтьsession.commit()
по завершению запроса (я не знаком с TurboGears, так что игнорировать это , если это обрабатывается для вас на уровне каркаса). Помимо проверки внесения ваших изменений в базу данных, это приведет к истечению срока действия всего в сеансе. Следующая транзакция не начнется до следующего использования этого сеанса, поэтому будущие запросы в том же потоке не увидят устаревшие данные.session.execute(Foo.__table__.insert(), values)
Обычно я использую
add_all
.источник
.add
их подключению к сеансу по одному?Add the given collection of instances to this Session.
ли у вас основания полагать, что он не выполняет массовую вставку?.add
каждый элемент отдельно.bulk_save_objects()
с aflush()
, мы можем получить идентификатор объекта, ноbulk_save_objects()
не можем (событие сflush()
called).Прямая поддержка была добавлена в SQLAlchemy с версии 0.8
Согласно документам ,
connection.execute(table.insert().values(data))
должно сработать. (Обратите внимание, что это не то же самое,connection.execute(table.insert(), data)
что приводит к множеству вставок отдельных строк через вызовexecutemany
). При любом подключении, кроме локального, разница в производительности может быть огромной.источник
SQLAlchemy представил это в версии
1.0.0
:Массовые операции - документация SQLAlchemy
С помощью этих операций теперь вы можете выполнять массовую вставку или обновление!
Например (если вам нужны минимальные накладные расходы для простых таблиц INSERT), вы можете использовать
Session.bulk_insert_mappings()
:Или, если хотите, пропустите
loadme
кортежи и напишите словари прямо в нихdicts
(но мне легче убрать всю многословность из данных и загрузить список словарей в цикле).источник
Ответ Пьера правильный, но одна проблема заключается в том, что
bulk_save_objects
по умолчанию не возвращаются первичные ключи объектов, если это вас беспокоит. Установите,return_defaults
чтобыTrue
получить такое поведение.Документация здесь .
источник
Все дороги ведут в Рим , но некоторые из них пересекают горы, требуются паромы, но если вы хотите добраться туда быстро, просто следуйте по автомагистрали.
В этом случае автомагистраль должна использовать функцию execute_batch () psycopg2 . Документация говорит об этом лучше всего:
Текущая реализация
executemany()
(с использованием крайне милосердных преуменьшений) не особенно эффективна. Эти функции можно использовать для ускорения повторного выполнения оператора с набором параметров. За счет уменьшения количества обращений к серверу производительность может быть на несколько порядков выше, чем при использованииexecutemany()
.В моем тесте
execute_batch()
это примерно в два раза быстрее , какexecutemany()
и дает возможность конфигурировать PAGE_SIZE для дальнейшей настройки (если вы хотите выжать последние 2-3% производительность из драйвера).Эту же функцию можно легко включить, если вы используете SQLAlchemy, установив
use_batch_mode=True
в качестве параметра при создании экземпляра движка с помощьюcreate_engine()
источник
execute_values
работает быстрее, чем psycopg2execute_batch
!Это способ:
Это будет вставлено так:
Ссылка: SQLAlchemy FAQ включает тесты для различных методов фиксации.
источник
Лучший ответ, который я нашел до сих пор, был в документации sqlalchemy:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Есть полный пример теста возможных решений.
Как показано в документации:
bulk_save_objects - не лучшее решение, но его производительность правильная.
Вторая лучшая реализация с точки зрения удобочитаемости, я думаю, была с ядром SQLAlchemy:
Контекст этой функции приведен в статье документации.
источник