SQLAlchemy: В чем разница между flush() и commit()?
Какая разница между flush()
а также commit()
в SQLAlchemy?
Я читал документы, но не мудрее - они, кажется, предполагают предварительное понимание, которого у меня нет.
Меня особенно интересует их влияние на использование памяти. Я загружаю некоторые данные в базу данных из ряда файлов (всего около 5 миллионов строк), и моя сессия иногда падает - это большая база данных и машина с небольшим объемом памяти.
Мне интересно, если я использую слишком много commit()
и не достаточно flush()
звонки - но без реального понимания, в чем разница, трудно сказать!
9 ответов
Объект 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')>]
Это не совсем ответ на исходный вопрос, но некоторые люди упоминали, что с 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). Это означает, что эти две операции атомарны.
Следовательно, если (B) нужен результат (A), вы хотите вызвать flush
после (A) и commit
после (B).
Кроме того, если session.autoflush is True
, за исключением случая, упомянутого выше или других в ответе Джимбо, вам не нужно звонитьflush
вручную.
TL; DR;
Используйте flush, когда вам нужно сделать запись, чтобы получить идентификатор первичного ключа из счетчика автоинкремента.
john=Person(name='John Smith', parent=None)
session.add(john)
session.flush()
son=Person(name='Bill Smith', parent=john.id)
Без промывки john никогда не получит идентификатор из БД и, следовательно, не сможет представить отношения родитель / потомок в коде.
Как говорили другие, без commit()
ничего из этого не будет постоянно сохраняться в БД.
Зачем сбрасывать, если можно совершить?
Как кто-то новичок в работе с базами данных и sqlalchemy, предыдущие ответы - это flush()
отправляет операторы SQL в БД и commit()
сохраняется их - мне было непонятно. Определения имеют смысл, но из определений не сразу понятно, почему вы должны использовать сброс, а не просто фиксацию.
Поскольку коммит всегда сбрасывается ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html), это звучит очень похоже. Я думаю, что большая проблема, которую следует выделить, заключается в том, что сброс не является постоянным и может быть отменен, тогда как фиксация является постоянной, в том смысле, что вы не можете попросить базу данных отменить последнюю фиксацию (я думаю)
@snapshoe подчеркивает, что если вы хотите запросить базу данных и получить результаты, включающие недавно добавленные объекты, вам необходимо сначала выполнить очистку (или выполнить фиксацию, которая будет очищена для вас). Возможно, это полезно для некоторых людей, хотя я не уверен, почему вы хотите сбросить, а не фиксировать (кроме тривиального ответа, что это можно отменить).
В другом примере я синхронизировал документы между локальной БД и удаленным сервером, и если пользователь решил отменить, все добавления / обновления / удаления должны быть отменены (т.е. частичной синхронизации нет, только полная синхронизация). При обновлении одного документа я решил просто удалить старую строку и добавить обновленную версию с удаленного сервера. Оказывается, из-за того, как написана sqlalchemy, порядок операций при фиксации не гарантируется. Это привело к добавлению дублирующей версии (перед попыткой удаления старой), что привело к сбоям в БД уникального ограничения. Чтобы обойти это, я использовалflush()
так что этот порядок поддерживался, но я все еще мог отменить, если позже процесс синхронизации не удался.
См. Мой пост по этому поводу: Есть ли порядок добавления или удаления при фиксации в sqlalchemy
Точно так же кто-то хотел знать, сохраняется ли порядок добавления при фиксации, т.е. если я добавляю object1
затем добавьте object2
, делает object1
быть добавленным в базу данных перед object2
Сохраняет ли SQLAlchemy порядок при добавлении объектов в сеанс?
Опять же, здесь предположительно использование flush() обеспечит желаемое поведение. Подводя итог, можно сказать, что одним из способов использования flush является обеспечение гарантии порядка (я думаю), опять же, позволяя себе возможность "отменить", которую коммит не предоставляет.
Автозапуск и автоматическая фиксация
Обратите внимание, что автозапуск можно использовать для обеспечения того, чтобы запросы действовали в обновленной базе данных, поскольку sqlalchemy будет сбрасывать данные перед выполнением запроса. https://docs.sqlalchemy.org/en/13/orm/session_api.html
Autocommit - это кое-что еще, что я не совсем понимаю, но похоже, что его использование не рекомендуется: https://docs.sqlalchemy.org/en/13/orm/session_api.html
Использование памяти
Теперь исходный вопрос действительно хотел узнать о влиянии сброса и фиксации для целей памяти. Поскольку способность сохраняться или нет - это то, что предлагает база данных (я думаю), простой промывки должно быть достаточно для разгрузки в базу данных - хотя фиксация не должна повредить (на самом деле, вероятно, помогает - см. Ниже), если вас не волнует отмена.
sqlalchemy использует слабые ссылки для объектов, которые были очищены: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html
Это означает, что если у вас нет объекта, явно удерживаемого где-то, например, в списке или dict, sqlalchemy не будет хранить его в памяти.
Однако тогда у вас есть проблемы с базой данных. Предположительно, сброс без фиксации требует некоторого ущерба памяти для поддержки транзакции. Опять же, я новичок в этом, но вот ссылка, которая, кажется, предлагает именно это: /questions/19993901/kak-umenshit-potreblenie-pamyati-sqlite/19993920#19993920
Другими словами, коммиты должны уменьшить использование памяти, хотя, по-видимому, здесь есть компромисс между памятью и производительностью. Другими словами, вы, вероятно, не захотите фиксировать каждое изменение базы данных по одному (из соображений производительности), но слишком долгое ожидание увеличит использование памяти.
Как говорит @snapshoe
flush()
отправляет ваши операторы SQL в базу данных
commit()
совершает транзакцию.
Когда session.autocommit == False:
commit() вызовет flush(), если ваш autoflush == True.
Когда session.autocommit == True:
Вы не можете вызвать commit(), если вы не начали транзакцию (чего вы, вероятно, не сделали, так как вы, вероятно, использовали бы только этот режим, чтобы избежать ручного управления транзакциями).
В этом режиме вы должны вызвать flush(), чтобы сохранить изменения ORM. Флеш эффективно также фиксирует ваши данные.
Существующие ответы не имеют большого смысла, если вы не понимаете, что такое транзакция базы данных . (Так было со мной до недавнего времени.)
Иногда вам может потребоваться запустить несколько операторов SQL, и они будут успешными или неудачными в целом . Например, если вы хотите выполнить банковский перевод со счета A на счет B, вам нужно будет выполнить два запроса, например
UPDATE accounts SET value = value - 100 WHERE acct = 'A'
UPDATE accounts SET value = value + 100 WHERE acct = 'B'
Если первый запрос выполнен успешно, а второй - нет, это плохо (по понятным причинам). Итак, нам нужен способ рассматривать эти два запроса «как единое целое». Решение состоит в том, чтобы начать с оператора BEGIN и заканчивать оператором COMMIT или оператором ROLLBACK, например
BEGIN
UPDATE accounts SET value = value - 100 WHERE acct = 'A'
UPDATE accounts SET value = value + 100 WHERE acct = 'B'
COMMIT
Это разовая транзакция.
В ORM SQLAlchemy это может выглядеть так:
# BEGIN issued here
acctA = session.query(Account).get(1) # SELECT issued here
acctB = session.query(Account).get(2) # SELECT issued here
acctA.value -= 100
acctB.value += 100
session.commit() # UPDATEs and COMMIT issued here
Если вы отслеживаете, когда выполняются различные запросы, вы увидите, что ОБНОВЛЕНИЯ не попадают в базу данных, пока вы не вызовете
session.commit()
.
В некоторых ситуациях вы можете захотеть выполнить операторы UPDATE перед выдачей COMMIT. (Возможно, база данных выдает объекту автоматически увеличивающийся идентификатор, и вы хотите получить его перед COMMITING). В этих случаях вы можете явно
flush()
сессия.
# BEGIN issued here
acctA = session.query(Account).get(1) # SELECT issued here
acctB = session.query(Account).get(2) # SELECT issued here
acctA.value -= 100
acctB.value += 100
session.flush() # UPDATEs issued here
session.commit() # COMMIT issued here
Для простой ориентации:
- вносит реальные изменения (они становятся видны в базе данных)
- вносит фиктивные изменения (они становятся видимыми только для вас)
Представьте, что базы данных работают как git-ветки.
- Прежде всего, вы должны понять, что во время
transaction
вы не манипулируете реальными данными базы данных. - Вместо этого вы получаете что-то вроде нового
branch
, а там балуйтесь. - Если в какой-то момент вы пишете команду , это означает: "
merge
мои данные-изменяются в основные данные БД ». - Но если вам нужны какие-то будущие данные, вы можете получить их только после
commit
(например, вставить в таблицу, и вам нужно вставить ), то вы используетеflush
команда, означающая: «Рассчитай мне будущее и зарезервируй его за мной » . - Тогда вы можете использовать это
PKID
значение далее в вашем коде и будьте уверены, что реальные данные будут такими, как ожидалось. -
Commit
всегда должен идти в конце, чтобы объединиться с основными данными БД.
commit () записывает эти изменения в базу данных. flush () всегда вызывается как часть вызова commit () (1). Когда вы используете объект Session для запроса к базе данных, запрос возвращает результаты как из базы данных, так и из окрашенных в красный цвет частей незарегистрированной транзакции, которую он выполняет.
Многие люди все еще в замешательстве, поэтому я попробую добавить свое собственное объяснение.
Почти все знают, что это за коммит, но сбрасывают что-то непонятное.
Если вы используете сеанс sqlalchemy для управления объектами ORM, вы должны понимать, что сеанс — это не объект вашей базы данных, а абстракция sqlalchemy . Все ваши манипуляции с объектами ORM «хранятся» в сеансе, пока вы не заставите sqlalchemy выполнять реальные запросы к БД. Обычно это происходит, когда менеджер контекста сеанса__exit__
вызывается непосредственно перед фиксацией. Но вы можете принудительно сделать это вручную.
>>> from main import Session, User
>>> session = Session()
>>> usr1 = User(name="John") # No SQL happen here
>>> usr1.name = "Kevin" # Also no SQL happen here
>>> usr1.id is None # ID is DB-side autoincrement, so it undefined
True
>>> session.add(usr1) # Probably you think that SQL INSERT sent, but no!
# User object just "attached" to session
>>> usr1.name = "Robert" # Manipulate over ORM object, but no single SQL sent
>>> session.flush() # Forcing sqlalchemy sync session state to DB
# First real SQL issued only there
INFO sqlalchemy.engine.Engine select pg_catalog.version()
INFO sqlalchemy.engine.Engine [raw sql] {}
INFO sqlalchemy.engine.Engine select current_schema()
INFO sqlalchemy.engine.Engine [raw sql] {}
INFO sqlalchemy.engine.Engine show standard_conforming_strings
INFO sqlalchemy.engine.Engine [raw sql] {}
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine INSERT INTO users (name) VALUES (%(name)s) RETURNING users.id
INFO sqlalchemy.engine.Engine [generated in 0.00009s] {'name': 'Robert'}
>>> session.commit() # It sent COMMIT to DB
INFO sqlalchemy.engine.Engine COMMIT
# ----- Let's play other way -----
>>> usr2 = User(name="other user") # No SQL
>>> session.add(usr2) # Still no SQL
>>> session\
.query(User)\
.filter_by(name="other user")\
.all() # Sqlalchemy flushing state before do SELECT
INFO sqlalchemy.engine.Engine INSERT INTO users (name) VALUES (%(name)s) RETURNING users.id
INFO sqlalchemy.engine.Engine [cached since 40.72s ago] {'name': 'other user'}
INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name
FROM users
WHERE users.name = %(name_1)s
INFO sqlalchemy.engine.Engine [generated in 0.00016s] {'name_1': 'other user'}
[User(id=6, name=other user)]
>>> session.flush() # So "flush" do nothing - session state already flushed
>>> session.commit() # Commit sent COMMIT, as expected
INFO sqlalchemy.engine.Engine COMMIT
>>> session.close()