Перенос вручную в переносе данных Django
Я бы хотел написать миграцию данных, где я изменяю все строки в большой таблице меньшими партиями, чтобы избежать проблем с блокировкой. Однако я не могу понять, как вручную выполнить перенос Django. Каждый раз, когда я пытаюсь запустить commit
, я получаю:
TransactionManagementError: это запрещено, когда активен блок "атомный".
AFAICT, редактор схемы базы данных всегда переносит миграции Postgres в атомный блок.
Есть ли разумный способ выйти из транзакции из миграции?
Моя миграция выглядит следующим образом:
def modify_data(apps, schema_editor):
counter = 0
BigData = apps.get_model("app", "BigData")
for row in BigData.objects.iterator():
# Modify row [...]
row.save()
# Commit every 1000 rows
counter += 1
if counter % 1000 == 0:
transaction.commit()
transaction.commit()
class Migration(migrations.Migration):
operations = [
migrations.RunPython(modify_data),
]
Я использую Django 1.7 и Postgres 9.3. Это использовалось для работы с южными и более старыми версиями Django.
Ответы
Ответ 1
Лучшее обходное решение, которое я нашел, вручную выходит из атомной области перед запуском миграции данных:
def modify_data(apps, schema_editor):
schema_editor.atomic.__exit__(None, None, None)
# [...]
В отличие от сброса connection.in_atomic_block
вручную, это позволяет использовать контекстный менеджер atomic
внутри миграции. Кажется, что нет более здравого способа.
В декораторе, который будет использоваться с операцией RunPython
, можно использовать логику разрыва транзакции (по общему признанию, беспорядочную):
def non_atomic_migration(func):
"""
Close a transaction from within code that is marked atomic. This is
required to break out of a transaction scope that is automatically wrapped
around each migration by the schema editor. This should only be used when
committing manually inside a data migration. Note that it doesn't re-enter
the atomic block afterwards.
"""
@wraps(func)
def wrapper(apps, schema_editor):
if schema_editor.connection.in_atomic_block:
schema_editor.atomic.__exit__(None, None, None)
return func(apps, schema_editor)
return wrapper
Обновление
Django 1.10 будет поддерживать неатомные миграции.
Ответ 2
Из документация о RunPython
:
По умолчанию RunPython будет запускать свое содержимое внутри транзакции в базах данных, которые не поддерживают DDL-транзакции (например, MySQL и Oracle). Это должно быть безопасным, но может привести к сбою, если вы попытаетесь использовать schema_editor, предоставленный на этих серверах; в этом случае, передайте atom = False в операцию RunPython.
Итак, вместо того, что у вас есть:
class Migration(migrations.Migration):
operations = [
migrations.RunPython(modify_data, atomic=False),
]