我有一个带有PostgreSQL数据库的生产Django部署(Django 1.11)。我想在我的一个模型中添加一个不可为空的字段:
class MyModel(models.Model):
new_field = models.BooleanField(default=False)
为了部署,我需要更新服务器上的代码或首先运行迁移,但由于这是一个生产部署,请求可以(并且将会)发生在我更新数据库和更新服务器之间。如果我先更新服务器,我会得到一个OperationalError no such column
,所以我显然需要先更新数据库。
但是,当我首先更新数据库时,在使用新代码更新之前,我从服务器上发出的请求中收到以下错误:
django.db.utils.IntegrityError:NOT NULL约束失败:myapp_mymodel.new_field
从表面上看,这没有任何意义,因为该字段具有默认值。进一步深入研究,似乎默认值仅由Django逻辑提供,而不是实际存储在SQL级别。如果服务器没有更新的代码,则它不会将列传递给SQL以进行更新,而SQL将其解释为NULL。
鉴于此,如何在不让用户收到任何错误的情况下将这个新的非可空字段部署到我的应用程序?
迁移应始终在部署开始时运行,否则您会遇到其他问题。此问题的解决方案是将更改拆分为两个部署。
在部署1中,字段需要可以为空(NullBooleanField
或null=True
)。您应该在此状态下对代码进行迁移,并确保如果字段的值为None
,则其余代码不会崩溃。这是必要的,因为请求可以发送到尚未拥有新代码的服务器;如果这些服务器创建模型的实例,则它们将创建该字段为null。
在部署2中,您将字段设置为不可为空,为此进行迁移,并删除您编写的任何额外代码以处理字段值为None
的情况。如果该字段没有默认值,则为第二次部署进行的迁移将需要在此字段中填写具有None
的对象的值。
虽然看起来有点不同,但还需要两种部署技术来安全地删除字段。在第一次部署中,您将从模型文件中删除该字段,并从代码中删除对它的所有引用,但不要部署默认迁移以删除该字段。相反,部署1具有自定义迁移,可将字段设置为可空。然后,在部署2中,部署迁移以实际从数据库中删除该字段。
你可以从NullBooleanField
开始:
new_field = models.NullBooleanField(default=False)
添加到您的模型中new_field = models.BooleanField(default=False)
如果旧的生产代码在步骤5和6之间写入表,则将写入空值new_field
。在步骤6和7之间将有一段时间,其中BooleanField可以有空值,并且当读取该字段时,它将为null。如果你的代码可以处理这个问题,你就可以了,然后第7步将所有这些空值转换为False。如果新代码无法处理这些空值,则可以执行以下步骤:
new_field = models.NullBooleanField(default=False)
添加到您的模型中new_field = models.BooleanField(default=False)
*请注意,这些方法仅使用Postgres进行测试。
通常,django升级过程如下所示:
当地发展ENV:
在生产服务器上: