SQLAlchemy: В чем разница между flush () и commit ()?

422

Какая разница между flush()и commit()в SQLAlchemy?

Я читал документы, но не мудрее - они, кажется, предполагают предварительное понимание, которого у меня нет.

Меня особенно интересует их влияние на использование памяти. Я загружаю некоторые данные в базу данных из ряда файлов (всего около 5 миллионов строк), и моя сессия иногда падает - это большая база данных и машина с небольшим объемом памяти.

Мне интересно, если я использую слишком много commit()и недостаточно flush()звонков - но без реального понимания, в чем разница, трудно сказать!

AP257
источник

Ответы:

534

Объект Session - это в основном текущая транзакция изменений в базе данных (обновление, вставка, удаление). Эти операции не сохраняются в базе данных до тех пор, пока они не будут зафиксированы (если ваша программа прерывается по какой-либо причине во время транзакции в середине сеанса, все незафиксированные изменения внутри нее теряются).

Сеансовый объект регистрирует транзакции операций session.add(), но еще не передает их в базу данных, пока не session.flush()будет вызван.

session.flush()передает серию операций в базу данных (вставка, обновление, удаление). База данных поддерживает их как ожидающие операции в транзакции. Изменения не сохраняются постоянно на диске или не видны другим транзакциям, пока база данных не получит COMMIT для текущей транзакции (что и session.commit()делает).

session.commit() фиксирует (сохраняет) эти изменения в базе данных.

flush()всегда вызывается как часть вызова commit()( 1 ).

Когда вы используете объект Session для запроса к базе данных, запрос будет возвращать результаты как из базы данных, так и из очищенных частей незафиксированной транзакции, которую он содержит. По умолчанию Session возражает против autoflushсвоих операций, но это можно отключить.

Надеюсь, этот пример прояснит ситуацию:

#---
s = Session()

s.add(Foo('A')) # The Foo('A') object has been added to the session.
                # It has not been committed to the database yet,
                #   but is returned as part of a query.
print 1, s.query(Foo).all()
s.commit()

#---
s2 = Session()
s2.autoflush = False

s2.add(Foo('B'))
print 2, s2.query(Foo).all() # The Foo('B') object is *not* returned
                             #   as part of this query because it hasn't
                             #   been flushed yet.
s2.flush()                   # Now, Foo('B') is in the same state as
                             #   Foo('A') was above.
print 3, s2.query(Foo).all() 
s2.rollback()                # Foo('B') has not been committed, and rolling
                             #   back the session's transaction removes it
                             #   from the session.
print 4, s2.query(Foo).all()

#---
Output:
1 [<Foo('A')>]
2 [<Foo('A')>]
3 [<Foo('A')>, <Foo('B')>]
4 [<Foo('A')>]
snapshoe
источник
Еще одна вещь: вы знаете, увеличивает ли вызов commit () объем используемой памяти или уменьшает ее?
AP257
2
Это также неверно для движков БД, которые не поддерживают транзакции, такие как myisam. Поскольку нет текущей транзакции, flush еще меньше отличается от коммита.
опустошил
1
@underrun Так, если я сделаю session.query() после session.flush(), я увижу свои изменения? Учитывая, что я использую MyISAM.
Замороженное пламя
1
Хорошо ли это или плохой стиль для использования flush()и commit(), или я должен оставить это до алхимии. Я использовал flush()в некоторых случаях, потому что последующие запросы должны были собрать новые данные.
Дженс
1
@Jens Use autoflush( Trueпо умолчанию). Он автоматически сбрасывается перед всеми запросами, поэтому вам не нужно запоминать каждый раз.
Киран Джонналагадда
24

Как говорит @snapshoe

flush() отправляет ваши операторы SQL в базу данных

commit() совершает транзакцию.

Когда session.autocommit == False:

commit()позвонит, flush()если вы установите autoflush == True.

Когда session.autocommit == True:

Вы не можете позвонить, commit()если не начали транзакцию (чего вы, вероятно, не сделали, поскольку вы, вероятно, использовали бы только этот режим, чтобы избежать ручного управления транзакциями).

В этом режиме вы должны позвонить, flush()чтобы сохранить изменения ORM. Флеш эффективно также фиксирует ваши данные.

Иаков
источник
24
"commit () вызовет flush (), если ваш autoflush == True." не совсем правильно, или просто вводит в заблуждение. Фиксация всегда сбрасывается, независимо от настройки автоматической промывки.
Илья Эвериля
3
Параметр autoflushуправляет тем, будет ли sqlalchemy сначала выдавать сброс, если имеются ожидающие записи перед выполнением запроса, и не имеет никакого отношения к управлению неизбежным сбросом при фиксации.
SuperShoot
4

Зачем флеш, если вы можете совершить?

Как кто-то новичок в работе с базами данных и sqlalchemy, предыдущие ответы - которые flush()отправляют операторы SQL в БД и commit()сохраняют их - были мне неясны. Определения имеют смысл, но из определений не сразу понятно, почему вы бы использовали сброс, а не просто фиксацию.

Поскольку коммит всегда сбрасывается ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ), эти звуки действительно похожи. Я думаю, что большая проблема, которую нужно подчеркнуть, заключается в том, что сброс не является постоянным и может быть отменен, тогда как фиксация является постоянной, в том смысле, что вы не можете попросить базу данных отменить последний коммит (я думаю)

@snapshoe подчеркивает, что если вы хотите запросить базу данных и получить результаты, которые включают вновь добавленные объекты, вам нужно сначала очистить (или зафиксировать, что для вас сбросит). Возможно, это полезно для некоторых людей, хотя я не уверен, почему вы хотели бы сбрасывать, а не фиксировать (кроме простого ответа, что его можно отменить).

В другом примере я синхронизировал документы между локальной БД и удаленным сервером, и если пользователь решил отменить, все добавления / обновления / удаления должны быть отменены (т. Е. Нет частичной синхронизации, только полная синхронизация). При обновлении одного документа я решил просто удалить старую строку и добавить обновленную версию с удаленного сервера. Оказывается, из-за способа написания sqlalchemy порядок операций при фиксации не гарантируется. Это привело к добавлению дублирующейся версии (перед попыткой удалить старую), что привело к тому, что БД не прошла уникальное ограничение. Чтобы обойти это, я использовал flush()так, чтобы порядок сохранялся, но я все еще мог отменить, если позже процесс синхронизации не удался.

Смотрите мой пост по этому адресу: Есть ли порядок добавления или удаления при фиксации в sqlalchemy?

Точно так же кто-то хотел знать, поддерживается ли порядок добавления при фиксации, то есть, если я добавляю, а object1затем добавляю object2, object1добавляется ли в базу данных раньше object2 Сохраняет ли SQLAlchemy порядок при добавлении объектов в сеанс?

Опять же, здесь, вероятно, использование flush () обеспечит желаемое поведение. Таким образом, в общем, одно из применений для сброса - предоставить гарантии заказа (я думаю), опять же, при этом оставляя себе возможность «отменить», которую не предоставляет commit.

Автозапуск и Автокоммит

Обратите внимание, что autoflush может использоваться для обеспечения того, чтобы запросы действовали в обновленной базе данных, поскольку sqlalchemy будет сбрасываться перед выполнением запроса. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

Autocommit - это еще кое-что, что я не совсем понимаю, но похоже, что его использование не рекомендуется: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params. автокоммит

Использование памяти

Теперь первоначальный вопрос на самом деле хотел узнать о влиянии flush на commit для памяти. Поскольку способность сохранять или нет - это то, что предлагает база данных (я думаю), простой очистки должно быть достаточно для разгрузки базы данных - хотя фиксация не должна повредить (на самом деле, вероятно, помогает - см. Ниже), если вы не заботитесь об отмене ,

sqlalchemy использует слабые ссылки для сброшенных объектов: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Это означает, что если у вас нет объекта, явно удерживаемого где-то, например, в списке или dict, sqlalchemy не будет хранить его в памяти.

Тем не менее, у вас есть проблемы с базой данных. Предположительно очистка без фиксации сопровождается некоторой потерей памяти для поддержки транзакции. Опять же, я новичок в этом, но вот ссылка, которая, кажется, предлагает именно это: https://stackoverflow.com/a/15305650/764365

Другими словами, коммиты должны уменьшить использование памяти, хотя, вероятно, здесь есть компромисс между памятью и производительностью. Другими словами, вы, вероятно, не хотите фиксировать каждое отдельное изменение базы данных, по одному (из соображений производительности), но слишком долгое ожидание увеличит использование памяти.

Джимбо
источник
1

Это не дает точного ответа на первоначальный вопрос, но некоторые люди упоминают, что с session.autoflush = Trueвами не нужно пользоваться session.flush()... И это не всегда так.

Если вы хотите использовать идентификатор недавно созданного объекта в середине транзакции , вы должны позвонить session.flush().

# Given a model with at least this id
class AModel(Base):
   id = Column(Integer, primary_key=True)  # autoincrement by default on integer primary key

session.autoflush = True

a = AModel()
session.add(a)
a.id  # None
session.flush()
a.id  # autoincremented integer

Это потому, autoflushчто НЕ выполняет автоматическое заполнение идентификатора (хотя запрос объекта будет, что иногда может вызвать путаницу, как в «почему это работает здесь, но не там?», Но Snapshoe уже охватил эту часть).


Один связанный аспект, который кажется мне довольно важным и не был упомянут на самом деле:

Почему бы вам не совершить все время? - Ответ атомарность .

Причудливый слово сказать: ансамбль операций должны все быть выполнены успешно или ни один из них не вступит в силу.

Например, если вы хотите создать / обновить / удалить какой-либо объект (A), а затем создать / обновить / удалить другой (B), но если (B) не удалось, вы хотите вернуть (A). Это означает, что эти 2 операции являются атомарными .

Следовательно, если (B) нужен результат (A), вы хотите позвонить flushпосле (A) и commitпосле (B).

Также, если session.autoflush is True, за исключением случая, который я упомянул выше или других в ответе Джимбо , вам не нужно будет звонить flushвручную.

Ромэн Винсент
источник