I signed up to participate in the 1st challenge of the Golden Stone Project - share the 100,000 prize pool, this is my nth article, click to view the details of the event
I. Introduction
When we add or modify the database model in django, we generally need to execute makemigrations and migrate to generate the corresponding database table for our model class, or modify the corresponding table structure. This is very convenient.
But when we execute these two commands in actual use, unexpected errors often occur. The following two commands are explained in detail for you, so that you can use them more calmly!
2. Detailed explanation and practical operation of migrate and makemigrations
1. makemigrations
makemigrations will generate database migration files for the model we wrote
For example, we create a model of a Product
class Product(models.Model):
id = models.AutoField(primary_key=True)
key = models.CharField(max_length=255)
created = models.DateTimeField()
suite_id = models.IntegerField(blank=True, null=True)
report_version_id = models.IntegerField(null=True)
class Meta:
db_table = 'client'
复制代码
Then execute the command python manage.py makemigrations
to generate a migration file (if no migration file is generated, remember to add the [apps.py] file and configure it, and then add its app name to INSTALLED_APPS) If we have multiple apps files, we can specify the app name to migrate the file. generate, commandpython manage.py makemigrations [app_label]
Usually we use this command enough!
Of course, there are other commands for us to use, such as execution python manage.py makemigrations --dry-run --verbosity 3
, code to generate migration files
Migration files can be python manage.py makemigrations --no-header
generated without django version and migration time annotations using
We can add configuration items managed=False
to the corresponding model code to ignore the migration and then execute makemigrations
the migration code at this time without generating the migration code for the model!
There are some other commands, but they are not commonly used, you can read the official documentation to understand
2. In the case of collaborative development, how to resolve conflicting migration files?
在类似于使用git做协同开发时,我们应该有一个规范就是团队中的每一个人都应该避免修改同一个model文件。但不可能保证每次的提交都能避免 migrations 的冲突!
这个时候我们可以使用python manage.py makemigrations --merge
进行合并来自动修复冲突,但这只适用于简单的model冲突合并。如果太复杂了建议阅读django的【writing-migrations】部分进行手动修改迁移文件
3. migrate
将迁移文件集同步到数据库中.
如果想指定某个app迁移的话可以使用python3 manage.py migrate [app_label]
如果想指定某个migrations文件的话可以使用python3 manage.py migrate [app_label] [migration_name]
例如:python manage.py migrate cases 0011_auto_20220726_1440
在我们使用django-admin startproject
创建一个项目时后,如果需要使用django 的用户管理、数据库迁移等功能时就还需要配置好数据库连接,然后执行migrate
数据库会生成这些表
在表【django_migrations】中会记录每次的mirage记录。
有个问题是,我们的项目并没有迁移文件,那migrate是走哪拿到迁移文件进行迁移的呢?
我们可以在【C:\Users\电脑用户名\AppData\Local\Programs\Python\Python39\Lib\site-packages\django\contrib\auth\migrations】下找到自带的用户模型迁移文件。
我们还可以加参数 --database DATABASE
来指定迁移的数据库
也可以使用--plan
显示将要执行的迁移计划
4. 迁移报错怎么办?
有些时候,我们直接对数据库表字段进行了修改操作,而没有修改对应的model代码时,再执行makemigrations
、migrate
会报错!
类似如下操作:
1)我们直接在数据库表中删除key这个字段
2)然后在对应的model代码中删除 【key】这个字段
3)这个时候再执行makemigrations
、migrate
,会发现migrate
的时候报错了
报错的原因是我们先在数据库中删除了key
这个字段,然后去修改的model文件进行迁移文件的生成和迁移。当migrate
的时候会执行删除key
这个操作,但我们的表里面已经没有这个字段了,所以会报错!
当遇到这种情况的时候,我们可以使用migrate --fake
来进行修复。
它会将将向目标的迁移操作标记为已应用,但不实际运行 SQL 来更改数据库结构。
另外使用migrate --fake-initial
可以对具有由CreateModel(建表操作)的迁移操作时,如果数据库表已经存在,则允许 Django 跳过应用程序的初始迁移 。此选项适用于首次针对预先存在使用迁移的数据库运行迁移时使用。但是,此选项不会检查匹配表名称之外的匹配数据库架构,因此只有在您确信现有架构与初始迁移中记录的架构匹配时才可以安全使用!
还有的其他命令操作不常用,需要了解可以参考官方文档
三、迁移生成的外键约束有必要吗
如果有外键的情况下,通过migrate
会在数据库中建立相应的外键约束。这是一个很不错的功能。在学校老师教学时,也会要求我们建立外键约束。
但在实际应用中并不是一个好的选择,而且在《阿里Java开发规范手册》中也明确规定:【强制】不得使用外键与级联,一切外键概念必须在应用层解决
为什么要做这样的规定呢?我们可以举一个例子来说明:
现在我们建立了两个Model:【product和project】,【project】的porduct
字段,关联Product
class Product(models.Model):
id = models.AutoField(primary_key=True)
created = models.DateTimeField()
product_name = models.CharField(max_length=100, null=True)
class Meta:
db_table = 'product'
class Project(models.Model):
id = models.AutoField(primary_key=True)
product = models.ForeignKey(to=Product, on_delete=models.PROTECT)
project_name = models.CharField(max_length=100, null=True)
class Meta:
db_table = 'project'
复制代码
然后我们进行迁移修改数据库表
可以看到【project】表有了一条外键约束的记录
当我们对【project】表增加一条project_id
为 1 的记录的时候,由于【product】表不存在相应的记录会导致报错:
可以看出,这个约束的存在,会保证表间数据的关系的完整性。更不容易出现脏数据。这是外键约束非常明显的优点!
但也存在着不可忽略的缺点:
性能问题
我们刚建立了两张表【project】和【product】,【project】表通过project_id
字段与【product】表做了外键约束。
这个时候,当我们每次往【project】表插入数据的时候,它会先去【product】中查询是否有对应的关联数据,如果通过程序来控制可以不进行这次查询。但设立了外键约束,就一定会去进行该查询。这实际是冗余的。当关联的字段少的时候可能没啥影响,但一但关联字段多了后,这种影响就尤其明显!
死锁
在我们每次修改【project】数据时,都需要去【product】表检查数据,需要获取额外的锁。如果在高并发大流量的事务场景下,外键约束更容易造成死锁!
开发/测试效率的降低
在我们日常的测试过程中,经常会遇到发现了一个BUG想复现或者方便测试的情况,会直接改数据库表的数据来达到方便测试的效果。
虽然这及不规范,但实际情况就是能够提升我们很多效率。这是毋庸置疑的!可是,这样的操作也会带来一些问题,比如因为数据导致的BUG,但实际并不是程序的BUG,或者发现不了一些潜在的BUG。
所以我的建议是:如果是业务相对复杂的话,可以在测试环境使用外键约束,但上了生产环境需要去掉!如果业务相对简单,那完全可以删除外键约束!
在django中,即便你删除了数据库中的外键约束,只要你model代码里的外键关系还在,则还是可以使用它的ORM进行外键操作的,没有区别!
四、反向迁移-inspectdb
inspectdb
命令会检查你的settings文件指向的数据库,将其数据库表生成对应的django模型代码并打印出来
也可以inspectdb
指定的模型 inspectdb product