为什么 Django 在添加新列时会删除 SQL DEFAULT 约束?
Why does Django drop the SQL DEFAULT constraint when adding a new column?
在最新的 Django (2.2) 中,当我向这样的模型添加新字段时:
new_field= models.BooleanField(default=False)
Django 运行s 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
约束?
我找到这张 2 年前的票:https://code.djangoproject.com/ticket/28000
stated in there 是:
Django uses database defaults to set values on existing rows in a table. It doesn't leave the default values in the database so dropping the default is correct behavior. There might be an optimization not to set/drop the default in this case -- I'm not sure it's needed since the column isn't null. A separate ticket could be opened for this.
我在这里的另一个问题中也看到了相同的引用:Django Postgresql dropping column defaults at migrate
再搜索一下,我发现了这个 SO 问题: that led to the code of the _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
情况的方式:/
Why not just keep the DEFAULT
constraint?
因为 Django 在应用程序级别而不是数据库级别处理 default
模型字段选项。所以真正的问题是为什么它设置 DEFAULT
约束。
但首先:Django 不使用数据库级默认值。 (来自the documentation: "Django never sets database defaults and always applies them in the Django ORM code."). This has been true from the beginning of the project. There has always been some interest in changing it (the first issue on the subject is 14 years old), and certainly other frameworks (Rails, I think, and SQLAlchemy)已经证明这是可能的。
但是除了向后兼容性之外,在应用程序级别处理默认值还有充分的理由。如:表达任意复杂计算的能力;不必担心跨数据库引擎的微妙不兼容性;在代码中实例化新实例并立即访问默认值的能力;在表单中向用户显示默认值的能力;等等。
基于关于该主题的两个 most recent 讨论,我认为没有人愿意将数据库默认值合并到 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 的支持,操作序列可能是也可能不是原子的。
在最新的 Django (2.2) 中,当我向这样的模型添加新字段时:
new_field= models.BooleanField(default=False)
Django 运行s 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
约束?
我找到这张 2 年前的票:https://code.djangoproject.com/ticket/28000
stated in there 是:
Django uses database defaults to set values on existing rows in a table. It doesn't leave the default values in the database so dropping the default is correct behavior. There might be an optimization not to set/drop the default in this case -- I'm not sure it's needed since the column isn't null. A separate ticket could be opened for this.
我在这里的另一个问题中也看到了相同的引用:Django Postgresql dropping column defaults at migrate
再搜索一下,我发现了这个 SO 问题:_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
情况的方式:/
Why not just keep the
DEFAULT
constraint?
因为 Django 在应用程序级别而不是数据库级别处理 default
模型字段选项。所以真正的问题是为什么它设置 DEFAULT
约束。
但首先:Django 不使用数据库级默认值。 (来自the documentation: "Django never sets database defaults and always applies them in the Django ORM code."). This has been true from the beginning of the project. There has always been some interest in changing it (the first issue on the subject is 14 years old), and certainly other frameworks (Rails, I think, and SQLAlchemy)已经证明这是可能的。
但是除了向后兼容性之外,在应用程序级别处理默认值还有充分的理由。如:表达任意复杂计算的能力;不必担心跨数据库引擎的微妙不兼容性;在代码中实例化新实例并立即访问默认值的能力;在表单中向用户显示默认值的能力;等等。
基于关于该主题的两个 most recent 讨论,我认为没有人愿意将数据库默认值合并到 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 的支持,操作序列可能是也可能不是原子的。