为什么Django在添加新列时会删除SQL DEFAULT约束?

Pet*_*ter 8 mysql django database-migration django-models django-migrations

在最新的Django(2.2)中,当我向这样的模型添加新字段时:

new_field= models.BooleanField(default=False)
Run Code Online (Sandbox Code Playgroud)

Django为MySQL运行以下命令:

ALTER TABLE `app_mymodel` ADD COLUMN `new_field` bool DEFAULT b'0' NOT NULL;
ALTER TABLE `app_mymodel` ALTER COLUMN `new_field` DROP DEFAULT;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

尽管在所有内容更新后都可以使用,但是这很成问题,因为在运行此迁移后,旧版本的应用程序无法再创建模型(他们不知道new_field)。为什么不仅仅保留DEFAULT约束?

Kev*_*nry 6

为什么不仅仅保留DEFAULT约束?

因为Django default在应用程序级别而不是数据库级别处理模型字段选项。因此,真正的问题是为什么它会设置DEFAULT所有约束。

但首先:Django不使用数据库级默认值。(根据文档:“ Django从不设置数据库默认值,并且始终将其应用于Django ORM代码中。”)。从项目开始就是如此。对其进行更改一直有一些兴趣(关于该主题的第一个问题已经有14年历史了),当然其他框架(Rails,我认为和SQLAlchemy)也已经表明有可能实现。

但是,除了向后兼容以外,还有很多理由可以在应用程序级别处理默认值。如:表达任意复杂计算的能力;不必担心跨数据库引擎的细微不兼容;具有在代码中实例化新实例并立即访问默认值的能力;以表格形式向用户显示默认值的能力;和更多。

基于这两个 近期关于这个问题的讨论中,我会说几乎没有食欲纳入数据库默认进入的语义default,但增加一个新的支持db_default选项。

现在,将新的非空字段添加到现有数据库是一个非常不同的用例。在这种情况下,您必须为数据库提供默认值才能执行该操作。makemigrations会尝试从default选项中推断出正确的值,如果不能,则会迫使您从命令行中指定一个值。因此,将DEFAULT修饰符用于此受限目的,然后将其删除。

您已经注意到,Django中缺少数据库级别的默认值会使连续部署变得更加困难。但是解决方案非常简单:只需在迁移中自己重新添加默认值即可。迁移系统的一大优点是,它可以轻松地在Django的ORM之外对数据库进行任意,可重复和可测试的更改。因此,只需添加一个新的RunSQL迁移操作:

operations = [
    # Add SQL for both forward and reverse operations
    migrations.RunSQL("ALTER TABLE app_mymodel ALTER COLUMN new_field SET DEFAULT 0;",
                      "ALTER TABLE app_mymodel ALTER COLUMN new_field DROP DEFAULT;")
]
Run Code Online (Sandbox Code Playgroud)

您可以将其放入新的迁移文件中,也可以直接编辑自动生成的文件。根据您的数据库及其对事务性DDL的支持,操作序列可能是原子的,也可能不是原子的。


Joh*_*fis 5

我发现2年前的这个票:https://code.djangoproject.com/ticket/28000
指出在那里说:

Django使用数据库默认值在表中的现有行上设置值。它不会在数据库中保留默认值,因此删除默认值是正确的行为。在这种情况下,可能有一种不设置/删除默认值的优化方法-由于该列不为null,因此我不确定是否需要使用默认值。为此可以另开一张票。

我还在另一个问题中看到了相同的参考:Django Postgresql在迁移时删除列默认值

再搜索一点,我遇到了这个问题:数据库中默认值的Django实现导致存在此注释的_alter_field方法django.db.backends.base.schema的代码:

# When changing a column NULL constraint to NOT NULL with a given
# default value, we need to perform 4 steps:
#  1. Add a default for new incoming writes
#  2. Update existing NULL rows with new default
#  3. Replace NULL constraint with NOT NULL
#  4. Drop the default again.
Run Code Online (Sandbox Code Playgroud)

尽管最后一个是关于将现有的可为空的字段更改为不可为空的字段,但这似乎是Django处理default情况的方式:/