为啥 Django 在添加新列时会删除 SQL DEFAULT 约束?
Posted
技术标签:
【中文标题】为啥 Django 在添加新列时会删除 SQL DEFAULT 约束?【英文标题】:Why does Django drop the SQL DEFAULT constraint when adding a new column?为什么 Django 在添加新列时会删除 SQL DEFAULT 约束? 【发布时间】:2020-01-24 09:28:12 【问题描述】:在最新的 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
constraint?
【问题讨论】:
这纯粹是一个理论问题,还是您也在寻找解决方案? 我正在寻找一种在没有null=True
的情况下安全地添加新字段的方法。用default=False
添加BooleanField
不起作用。这会使旧版本的应用崩溃。
我不明白这最后的评论。您要么想要一个具有两种状态或三种状态的字段:models.BooleanField()
或 models.BooleanField(null=True)
。无论哪种方式,除非您指定默认值,否则默认值为 None
,就像您在示例中所做的那样。
【参考方案1】:
为什么不只保留
DEFAULT
约束?
因为 Django 在应用程序级别而不是数据库级别处理 default
模型字段选项。所以真正的问题是它为什么要设置DEFAULT
约束。
但首先:Django 不使用数据库级别的默认值。 (来自the documentation:“Django 从不设置数据库默认值,总是在 Django ORM 代码中应用它们。”)。从项目一开始就是如此。一直有人对更改它感兴趣(这个主题的 first issue 已有 14 年历史),当然其他框架(我认为是 Rails 和 SQLAlchemy)已经表明这是可能的。
但是,除了向后兼容之外,还有很多理由可以在应用程序级别处理默认值。如:表达任意复杂计算的能力;不必担心跨数据库引擎的细微不兼容性;能够在代码中实例化新实例并立即访问默认值;能够在表单中向用户呈现默认值;等等。
基于关于该主题的两个mostrecent 讨论,我想说将数据库默认值合并到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;")
]
您可以将其放入new migration file 或简单地编辑自动生成的。根据您的数据库及其对事务性 DDL 的支持,操作序列可能是原子的,也可能不是原子的。
【讨论】:
我认为这是正确的答案。我自己不是 RunSQL 迁移的粉丝,但也许我应该重新考虑。【参考方案2】:我找到了这张 2 年前的票:https://code.djangoproject.com/ticket/28000 是stated in there:
Django 使用数据库默认值来设置表中现有行的值。它不会在数据库中保留默认值,因此删除默认值是正确的行为。在这种情况下可能有一个优化不设置/删除默认值 - 我不确定它是否需要,因为该列不为空。可以为此单独开一张票。
我在这里的另一个问题中也看到了相同的参考:Django Postgresql dropping column defaults at migrate
再搜索一下,我发现了这个 SO 问题:Django implementation of default value in database 导致出现此评论的 _alter_field
method from 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
案例的方式:/
【讨论】:
感谢您的参考!我不确定我是否理解为什么。而且我仍然不明白如果没有null=True
,我怎么能安全地添加新字段,但这是一个单独的问题。
@Petter 也许如果您尝试添加新字段,然后当您即将应用迁移./manage.py migrate
它会要求您提供默认值。 (我没有尝试过,所以我只是建议尝试一下)
创建迁移时应该出现该问题,而不是应用。
@Petter 没错。那么这是否有效或导致相同的行为?
您似乎在询问是否要添加没有默认值且 null=False 的字段?之后,旧版本的软件也会崩溃。以上是关于为啥 Django 在添加新列时会删除 SQL DEFAULT 约束?的主要内容,如果未能解决你的问题,请参考以下文章