---
title: "How to create database migrations"
version: 4.0
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/4.0/howto/writing-migrations/
canonical: https://djangodocs.dev/zh-hans/4.0/howto/writing-migrations/
---
# How to create database migrations

本文介绍了如何为可能遇到的不同场景组织和编写数据库迁移。关于迁移的介绍性资料，参考 [专题指南](/zh-hans/4.0/topics/migrations/)。

## 数据迁移和多种数据库

使用多种数据库时，你可能需要指定是否为特定数据库运行迁移。例如，你可能 **只想** 为特定数据库运行迁移。

为此，你可以在 `RunPython` 操作中检查数据库连接别名，通过查看 `schema_editor.connection.alias` 属性:

```
from django.db import migrations

def forwards(apps, schema_editor):
    if schema_editor.connection.alias != 'default':
        return
    # Your migration code goes here

class Migration(migrations.Migration):

    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(forwards),
    ]
```

你也能提供会以 `**hints` 传递给数据库路由器的 [`allow_migrate()`](/zh-hans/4.0/topics/db/multi-db/#allow_migrate) 方法的提示：

*`myapp/dbrouters.py`*

```python
class MyRouter:

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if 'target_db' in hints:
            return db == hints['target_db']
        return True
```

然后，要将其在迁移中生效，像下面这样做:

```
from django.db import migrations

def forwards(apps, schema_editor):
    # Your migration code goes here
    ...

class Migration(migrations.Migration):

    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(forwards, hints={'target_db': 'default'}),
    ]
```

若你的 `RunPython` 或 `RunSQL` 操作只影响了一个模型，为其传入 `model_name` 作为提示，使其对路由器更加透明。这对可复用的和第三方应用特别重要。

## 添加独一无二字段的迁移

应用 “普通” 迁移，将新的唯一非空的字段添加到已拥有一些行的表格会抛出一个错误，因为用于填充现有行的值只生成一次，从而打破了唯一约束。唯一非空字段即所有行的该字段都不能为空，且值唯一，不能重复。

因此，需要做以下步骤。在本例中，我们将添加一个带默认值的非空 [`UUIDField`](/zh-hans/4.0/ref/models/fields/#django.db.models.UUIDField)。根据你的需要修改对应字段。

- 在模型中以 `default=uuid.uuid4` 和 `unique=True` 参数添加该字段（根据字段类型，为其选择一个合适的默认值）。
- 运行 [`makemigrations`](/zh-hans/4.0/ref/django-admin/#django-admin-makemigrations) 命令。这将生成一个 `AddField` 操作的迁移。
- 通过运行 `makemigrations myapp --empty` 两次为同一应用生成两个相同的空迁移文件。我们已在以下例子中将迁移文件重命名成有意义的名字。
- 从自动生成的迁移（3个新文件中的第一个）中将 `AddField` 操作拷贝至上一个迁移，将 `AddField` 改为 `AlterField`，添加 `uuid` 和 `models` 的导入。例子：

  *`0006_remove_uuid_null.py`*

  ```python
  # Generated by Django A.B on YYYY-MM-DD HH:MM
  from django.db import migrations, models
  import uuid

  class Migration(migrations.Migration):

      dependencies = [
          ('myapp', '0005_populate_uuid_values'),
      ]

      operations = [
          migrations.AlterField(
              model_name='mymodel',
              name='uuid',
              field=models.UUIDField(default=uuid.uuid4, unique=True),
          ),
      ]
  ```
- 编辑第一个迁移文件。生成的迁移类应该看起来像这样：

  *`0004_add_uuid_field.py`*

  ```python
  class Migration(migrations.Migration):

      dependencies = [
          ('myapp', '0003_auto_20150129_1705'),
      ]

      operations = [
          migrations.AddField(
              model_name='mymodel',
              name='uuid',
              field=models.UUIDField(default=uuid.uuid4, unique=True),
          ),
      ]
  ```

  将 `unique=True` 改为 `null=True`——这将创建中间 null 字段，并延迟创建唯一性约束，直到我们已为所以行填充了唯一值。
- 在第一个空的迁移文件中，添加一个 [`RunPython`](/zh-hans/4.0/ref/migration-operations/#django.db.migrations.operations.RunPython) 或 [`RunSQL`](/zh-hans/4.0/ref/migration-operations/#django.db.migrations.operations.RunSQL) 操作，为每个已存在的行创建一个唯一值（本例中 UUID）。同时添加 `uuid` 的导入。例子：

  *`0005_populate_uuid_values.py`*

  ```python
  # Generated by Django A.B on YYYY-MM-DD HH:MM
  from django.db import migrations
  import uuid

  def gen_uuid(apps, schema_editor):
      MyModel = apps.get_model('myapp', 'MyModel')
      for row in MyModel.objects.all():
          row.uuid = uuid.uuid4()
          row.save(update_fields=['uuid'])

  class Migration(migrations.Migration):

      dependencies = [
          ('myapp', '0004_add_uuid_field'),
      ]

      operations = [
          # omit reverse_code=... if you don't want the migration to be reversible.
          migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
      ]
  ```
- 现在你能像往常一样用 [`migrate`](/zh-hans/4.0/ref/django-admin/#django-admin-migrate) 应用迁移了。

  注意，若你允许运行迁移时创建对象可能会造成竞争。 `AddField` 后和 `RunPython` 前创建的对象保留原先重写的 `uuid` 值。

### 非原子性迁移

对于支持 DDL 事务的数据库 (SQLite and PostgreSQL)，迁移默认运行在事务内。对于类似在大数据表上运行数据迁移的场景，你可以通过将 `atomic` 属性置为 `False` 避免在事务中运行迁移:

```
from django.db import migrations

class Migration(migrations.Migration):
    atomic = False
```

在这样的迁移种，所有的操作运行时都不含事务。通过使用 [`atomic()`](/zh-hans/4.0/topics/db/transactions/#django.db.transaction.atomic) 或为 `RunPython` 传入 `atomic=True` 能将部分迁移置于事务之中。

这是一个例子，关于非原子性数据迁移操作，将更新大数据表的操作分为数个小批次:

```
import uuid

from django.db import migrations, transaction

def gen_uuid(apps, schema_editor):
    MyModel = apps.get_model('myapp', 'MyModel')
    while MyModel.objects.filter(uuid__isnull=True).exists():
        with transaction.atomic():
            for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
                row.uuid = uuid.uuid4()
                row.save()

class Migration(migrations.Migration):
    atomic = False

    operations = [
        migrations.RunPython(gen_uuid),
    ]
```

`atomic` 属性对不支持 DDL 事务的数据库没有影响（例如 MySQL，Oracle）。（MySQL 的 [原子性 DDL 语句支持](https://dev.mysql.com/doc/refman/en/atomic-ddl.html) 指向独立的语句，而不是封装在能回滚的事务中的多句语句。）

## 控制迁移顺序

Django 不是通过迁移的名字决定迁移执行顺序，而是通过在 `迁移` 类上使用两个属性：  `dependencies` 和 `run_before`。

若你用过 [`makemigrations`](/zh-hans/4.0/ref/django-admin/#django-admin-makemigrations) 命令，你可能早已在运行时见过 `dependencies`，因为自动创建的迁移将此定义为其创建过程的一部分。

`依赖` 属性像这样申明:

```
from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0123_the_previous_migration'),
    ]
```

通常这就够用了，但是有很多次，你总是需要确认你的迁移运行在其它迁移 *之前*。例如，这对于让第三方应用的迁移运行在替换 [`AUTH_USER_MODEL`](/zh-hans/4.0/ref/settings/#std-setting-AUTH_USER_MODEL) 之后就很有用。

要实现此目的，将所有需要先运行的迁移置于你的 `Migration` 类的 `run_before` 属性:

```
class Migration(migrations.Migration):
    ...

    run_before = [
        ('third_party_app', '0001_do_awesome'),
    ]
```

尽可能使用 `dependencies`，而不是 `run_before`。只有在在特定迁移中添加 `dependencies` 使其运行于你编写的迁移之后是没希望的和不切实际的情况下，你才能使用 `run_before`。

## 在第三方应用程序中迁移数据

你可以使用数据迁移把数据从一个第三方应用程序中转移到另一个。

如果你计划要移除旧应用程序，则需要根据是否安装旧应用程序来设置 `依赖` 属性。否则，一旦你卸载旧应用程序，就会缺失依赖项。同样，你需要在调用 `app.get_model()` 时捕获 [`LookupError`](https://docs.python.org/3/library/exceptions.html#LookupError)，前者在旧应用程序中检索模型。这种方法允许你在任何地方部署项目，而无需先安装并且卸载旧应用程序。

这是一个迁移示例：

*`myapp/migrations/0124_move_old_app_to_new_app.py`*

```python
from django.apps import apps as global_apps
from django.db import migrations

def forwards(apps, schema_editor):
    try:
        OldModel = apps.get_model('old_app', 'OldModel')
    except LookupError:
        # The old app isn't installed.
        return

    NewModel = apps.get_model('new_app', 'NewModel')
    NewModel.objects.bulk_create(
        NewModel(new_attribute=old_object.old_attribute)
        for old_object in OldModel.objects.all()
    )

class Migration(migrations.Migration):
    operations = [
        migrations.RunPython(forwards, migrations.RunPython.noop),
    ]
    dependencies = [
        ('myapp', '0123_the_previous_migration'),
        ('new_app', '0001_initial'),
    ]

    if global_apps.is_installed('old_app'):
        dependencies.append(('old_app', '0001_initial'))
```

另外在迁移未执行时，请考虑好什么是你想要发生的。你可以什么都不做（就像上面的示例）或者从新应用中移除一些或全部的数据。相应的调整 [`RunPython`](/zh-hans/4.0/ref/migration-operations/#django.db.migrations.operations.RunPython) 操作的第二个参数。

## 通过使用 `through` 模型来更改 `ManyToManyField` 字段。

如果您将类 [`ManyToManyField`](/zh-hans/4.0/ref/models/fields/#django.db.models.ManyToManyField) 更改为使用 `through` 模型，默认迁移将删除现有表并创建新表，从而丢失现有关系。 为避免这种情况，您可以使用类 [`SeparateDatabaseAndState`](/zh-hans/4.0/ref/migration-operations/#django.db.migrations.operations.SeparateDatabaseAndState) 将现有表重命名为新表名，同时告诉迁移自动检测器已创建新模型。 您可以通过 [`sqlmigrate`](/zh-hans/4.0/ref/django-admin/#django-admin-sqlmigrate) 或 [`dbshell`](/zh-hans/4.0/ref/django-admin/#django-admin-dbshell) 检查现有表名。您可以使用直通模型的 `_meta.db_table` 属性检查新表名称。 您的新 `through` 模型应该使用与 Django 相同的 `ForeignKey`s 名称。 此外，如果它需要任何额外的字段，它们应该在类 [`SeparateDatabaseAndState`](/zh-hans/4.0/ref/migration-operations/#django.db.migrations.operations.SeparateDatabaseAndState) 之后添加到操作中。

例如，假如你有一个 `Book` 模型，它通过 `ManyToManyField`  链接 `Author` 模型，我们可以通过像下面这样添加一个带有新字段 `is_primary` 的中间模型 `AuthorBook`

```
from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):
    dependencies = [
        ('core', '0001_initial'),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                # Old table name from checking with sqlmigrate, new table
                # name from AuthorBook._meta.db_table.
                migrations.RunSQL(
                    sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
                    reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
                ),
            ],
            state_operations=[
                migrations.CreateModel(
                    name='AuthorBook',
                    fields=[
                        (
                            'id',
                            models.AutoField(
                                auto_created=True,
                                primary_key=True,
                                serialize=False,
                                verbose_name='ID',
                            ),
                        ),
                        (
                            'author',
                            models.ForeignKey(
                                on_delete=django.db.models.deletion.DO_NOTHING,
                                to='core.Author',
                            ),
                        ),
                        (
                            'book',
                            models.ForeignKey(
                                on_delete=django.db.models.deletion.DO_NOTHING,
                                to='core.Book',
                            ),
                        ),
                    ],
                ),
                migrations.AlterField(
                    model_name='book',
                    name='authors',
                    field=models.ManyToManyField(
                        to='core.Author',
                        through='core.AuthorBook',
                    ),
                ),
            ],
        ),
        migrations.AddField(
            model_name='authorbook',
            name='is_primary',
            field=models.BooleanField(default=False),
        ),
    ]
```

## 将非托管模型变为托管的

如果你想要将非托管模型 ([`managed=False`](/zh-hans/4.0/ref/models/options/#django.db.models.Options.managed)) 变为托管的，你必须移除 `managed=False` 并且在对此模型做其他模式相关的改变前生成一次迁移，因为如果迁移中出现模式改变，对 `Meta.managed` 的修改操作不会被执行。
