Подтвердить что ты не робот

Перенос вручную в переносе данных 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.

4b9b3361

Ответ 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),
  ]