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

问题描述 投票:0回答:2

在最新的 Django (2.2) 中,当我向模型添加新字段时,如下所示:

new_field= models.BooleanField(default=False)

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;

虽然这在所有内容都更新时有效,但这是非常有问题的,因为旧版本的应用程序在运行此迁移后无法再创建模型(它们不知道

new_field
)。为什么不只保留
DEFAULT
约束呢?

mysql django django-models database-migration django-migrations
2个回答
15
投票

为什么不只保留

DEFAULT
约束呢?

因为 Django 在应用程序级别处理

default
模型字段选项,而不是数据库级别。所以真正的问题是为什么它设置
DEFAULT
约束。

关于第一点,Django 历史上不支持模型字段的数据库级默认值。一直有人对改变这一点感兴趣(关于这个主题的第一期已经有 18 年历史了),当然其他框架(我认为是 Rails 和 SQLAlchemy)已经表明这是可能的。

最近 Django 添加了该选项,并有一个名为

db_default
的单独字段选项。不过,
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;")
]

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


5
投票

我找到了这张2年前的票:https://code.djangoproject.com/ticket/28000
其中指出:

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

我还在另一个问题中看到了相同的参考: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.

虽然最后一个是关于将现有的可为空字段更改为不可为空字段,但这似乎是 Django 处理

default
情况的方式 :/

© www.soinside.com 2019 - 2024. All rights reserved.