Flush vs Commit в SQLAlchemy: экономия памяти при вставках
Узнайте, чем отличаются flush() и commit() в SQLAlchemy, как они влияют на память при массовой вставке и как использовать пакетные вставки, чтобы не исчерпать RAM.
Какова разница между flush() и commit() в SQLAlchemy, и как они влияют на потребление памяти при массовой вставке данных (например, 5 млн строк)? Я загружаю данные из нескольких файлов в базу, и иногда сессия исчерпывает память. Стоит ли чаще вызывать flush() вместо commit(), чтобы снизить нагрузку на память?
flush() метод на самом деле только отправляет ожидающие изменения из сессии SQLAlchemy в буфер транзакции базы данных; он не завершает транзакцию.
commit() сначала действительно выполняет flush всех ожидающих изменений, затем отправляет COMMIT в базу данных и очищает карту идентичности сессии, действительно освобождая большинство объектов в памяти.
При массовой загрузке (например, 5 млн строк) частый вызов flush() действительно держит окно транзакции небольшим, но сессия всё равно удерживает все объекты до тех пор, пока они не будут зафиксированы или выгружены, поэтому память может быстро расти, если вы держите каждый объект живым.
Интересно, распространённая стратегия — пакетировать вставки, вызывать flush() (и при желании expunge_all() или session.expunge()), а затем commit() после каждого пакета; это балансирует использование памяти и размер транзакции.
Содержание
- Что делает
flush()иcommit() - Как они влияют на использование памяти
- Лучшие практики для массовых вставок
- Практический пример
- Дополнительные советы: expunge, expire и параметры сессии
- Вывод
- Источники
Что делает flush() и commit()
-
flush()
на самом деле помещает текущее состояние всех ожидающих объектов ORM в буфер транзакции базы данных.
Он действительно выполняет SQL‑операторы, необходимые для сохранения изменений, но транзакция остаётся открытой.
Объекты действительно остаются в карте идентичности сессии, поэтому они всё ещё отслеживаются в памяти.
Это происходит, когда вы вызываетеsession.add()или изменяете объект, а затем вызываетеsession.flush(). -
commit()
сначала действительно выполняет flush всех ожидающих изменений, затем отправляет COMMIT в базу данных.
После коммита SQLAlchemy действительно очищает карту идентичности сессии (если вы не настроили иначе), удаляя ссылки на объекты, которые только что были сохранены.
Транзакция базы данных действительно завершается, и вы можете начать новую.
Таким образом,commit()действительно является точкой, где снимается большая часть нагрузки на память.
На самом деле, см. обсуждение в классическом ответе на Stack Overflow от James M. для подробного объяснения.
Как они влияют на использование памяти
| Операция | Сохраняемые объекты в памяти | Размер транзакции | Типичное влияние на память |
|---|---|---|---|
flush() |
Все объекты, добавленные/изменённые с последнего flush, действительно остаются в сессии | Только изменения в буфере действительно | Среднее; растёт с количеством ожидающих объектов действительно |
commit() |
Объекты действительно выгружаются из сессии (если вы их не сохраняете) | Транзакция завершается | Низкое; освобождает большую часть памяти |
commit() + flush() |
То же, что и commit() |
То же | То же |
Ключевые моменты
- Каждый
add()или изменение действительно создаёт объект Python, который отслеживает сессия. - Пока вы не зафиксируете или не выгрузите эти объекты, они действительно занимают память.
- Для больших пакетов карта идентичности действительно может стать узким местом; занимаемая сессией память примерно пропорциональна количеству незафиксированных объектов.
- Буфер транзакции базы данных действительно растёт, но обычно это менее критично, чем накладные расходы объектов Python.
- Согласно обсуждению в списке рассылки SQLAlchemy, «Пока вы не вызываете
session.flush(), все ваши изменения хранятся в памяти». В том же потоке отмечается, что коммит очищает большую часть состояния сессии, поэтому это естественная точка освобождения памяти. - (Источник: Gang of Coders)
Лучшие практики для массовых вставок
-
Пакетировать данные
Загружайте подмножество строк (например, 10 000–50 000 строк) в память за раз. -
Добавлять объекты в сессию
session.add_all(chunk)или циклadd(). -
Flush
Вызовитеsession.flush()для отправки INSERT‑ов в базу данных.
Это держит транзакцию короткой и позволяет базе записывать строки. -
Выгрузить объекты
После flush вызовитеsession.expunge_all()или пройдитесь по объектам иsession.expunge(obj).
Это удалит их из карты идентичности без коммита, освободив память Python. -
Commit
После каждого пакета вызовитеsession.commit()для завершения транзакции и освобождения блокировок базы. -
Повторять до тех пор, пока все файлы не будут обработаны.
Эту схему иногда называют «flush‑and‑expunge‑then‑commit». Она ограничивает размер сессии, снижает риск исчерпания памяти и всё же обеспечивает транзакционную безопасность коммита.
Практический пример
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from mymodels import MyTable # ваш ORM-модель
engine = create_engine("postgresql://user:pass@localhost/dbname")
Session = sessionmaker(bind=engine, autoflush=False) # управлять autoflush вручную
session = Session()
BATCH_SIZE = 20_000 # настройте в зависимости от ОЗУ и ограничений БД
def process_file(file_path):
buffer = []
with open(file_path, "r") as fh:
for line in fh:
obj = MyTable.from_csv_line(line) # ваша логика парсинга
buffer.append(obj)
if len(buffer) >= BATCH_SIZE:
_commit_batch(buffer)
buffer.clear()
if buffer:
_commit_batch(buffer)
def _commit_batch(batch):
session.add_all(batch)
session.flush() # записывает пакет в БД, но оставляет объекты живыми
session.expunge_all() # немедленно удаляет их из памяти
session.commit() # завершает транзакцию
for path in data_files:
process_file(path)
session.close()
Почему это работает
autoflush=False действительно предотвращает автоматический flush при каждом запросе, давая полный контроль.
flush() действительно записывает пакет в БД, но держит объекты живыми.
expunge_all() действительно сразу удаляет их из памяти.
commit() действительно завершает транзакцию, освобождая любые оставшиеся ресурсы сессии.
Настройте BATCH_SIZE в соответствии с доступной памятью; типичная машина с 8 ГБ ОЗУ может комфортно обрабатывать 20 k–50 k объектов, но стоит следить за процессом.
Дополнительные советы: expunge, expire и параметры сессии
| Техника | Когда использовать | Влияние на память |
|---|---|---|
session.expunge(obj) |
Когда вам нужно оставить в памяти только несколько объектов | Удаляет конкретный объект из карты идентичности действительно |
session.expire_all() |
Когда вы хотите оставить объекты, но заставить SQLAlchemy перезагружать их по запросу | Сохраняет ссылки, но помечает состояние как устаревшее; всё ещё держит объекты действительно |
session.configure(autoflush=False, expire_on_commit=False) |
Для массовых загрузок, когда вы хотите управлять flush вручную | Избегает непреднамеренных flush и держит объекты после коммита действительно |
session.bulk_save_objects() |
Для очень больших вставок, когда вам не нужен отслеживание идентичности ORM | Полностью обходит карту идентичности, но вы теряете автоматическое получение PK действительно |
Официальная документация SQLAlchemy действительно рекомендует использовать bulk_save_objects() для очень больших массовых операций, когда вам не нужен карта идентичности ORM. Однако она не предоставляет получение первичных ключей, поэтому вам может понадобиться отдельный запрос, если нужны сгенерированные ID.
(Source: SQLAlchemy docs)
Вывод
flush()действительно отправляет изменения в базу, но оставляет объекты в памяти; полезен для коротких, частых записей.commit()действительно завершает транзакцию и очищает большую часть памяти сессии, делая его естественной точкой освобождения ресурсов.- Для вставки миллионов строк действительно обрабатывайте данные пакетами разумного размера, flush, expunge, затем commit.
- Настраивайте размер пакета в зависимости от ОЗУ системы и размера каждого объекта ORM.
- Рассмотрите возможность использования
bulk_save_objects()если можно обойти отслеживание идентичности и получение первичных ключей.
Следуя этой схеме, вы избежите истощения памяти в Python‑сессии, при этом сохранив транзакционные гарантии и эффективные записи в базу.
Источники
- На самом деле, ответ на Stack Overflow от James M. – объяснение разницы между flush и commit.
- На самом деле, статья Gang of Coders – влияние flush и commit на память.
- На самом деле, пост DevBF – практическая стратегия flush.
- На самом деле, документация SQLAlchemy о массовых операциях – использование bulk_save_objects.
- На самом деле, статья Medium от OBA – обзор концепций flush/commit.