持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
1. 背景
最近部署项目在测试环境的时候,出现了一个小bug,本着不可能有bug的心态下,按照以下的调试方式去查问题。
1. 本地环境和测试环境代码一毛一样,多一毛都不行,测试发现,哎呀本地环境正常啊,那肯定不是代码的问题(不是我的问题!!!)
2. 肯定是浏览器的问题,无痕+清缓存+重启浏览器,居然还是有问题,不可能啊
3. 老老实实的一步一步分析,最后发现是数据库的问题
仔细想了一下,毕竟这代码我前两天都已经部署上去了,如果数据库当时有问题,早就会报错了,为啥搁到今天?
后面仔细想了一下,好像这两天有同事在上面切换分支部署过代码,难道是同事搞的?(妥妥的甩锅!!!)
但是本着不能随意丢锅,然后问了一下同事,同事说这情况,他部署他分支的时候也发现了同样的问题,然后他只是重新把他的字段migrate了。
看到这里,其实广大机智聪明睿智的网友应该可以知道,很大问题出现了在数据migrate 的时候。
2. 分析模拟
后面我本地模拟分析了这种情况,开了两个分支,test-0.0.1分支, test-0.0.2分支 在test-0.0.1分支上,django model层的代码是这样子
class DingUser(models.Model):
"""
保存用户信息, name + userid
"""
objects = models.Manager()
name = models.CharField(max_length=64, db_index=True, null=False, blank=False, verbose_name='用户名称')
user_id = models.CharField(max_length=64, db_index=True, null=False, blank=False, verbose_name='钉钉里面的userid')
然后执行一下数据migrate的执行代码
python manage.py makemigrations
python manage.py migrate
先查看了一下migrations文件夹下的文件,正常生成了对应映射0001_initial.py文件 perfect, 很完美,再检查数据库,对应的数据表和字段也都生成了,没问题。
然后在test-0.0.2分支上, django model层的代码增加了几个字段,变成了这样子
class DingUser(models.Model):
"""
保存钉钉用户信息, name + userid
"""
objects = models.Manager()
name = models.CharField(max_length=64, db_index=True, null=False, blank=False, verbose_name='用户名称')
user_id = models.CharField(max_length=64, db_index=True, null=False, blank=False, unique=True, verbose_name='钉钉里面的userid')
email = models.CharField(max_length=64, db_index=True, null=True, blank=True, verbose_name='钉钉里面的email')
org_email = models.CharField(max_length=64, db_index=True, null=True, blank=True, verbose_name='钉钉里面的org_email')
union_id = models.CharField(max_length=64, db_index=True, null=True, blank=True, verbose_name='钉钉里面的unionid')
执行上面一样的步骤
python manage.py makemigrations
python manage.py migrate
控制台输出了下面的信息
migrations文件夹下的文件,生成了对应映射0002_auto_20220527_1048.py文件,这个文件是基于0001_initial.py,在里面新增了test-0.0.2分支的字段。
数据库表里面对应新增的字段也都存在,没有任何的问题,妥妥的!
按道理到了这块是不是都没有啥问题,问题在后面!!!
重新切换到test-0.0.1分支,然后model并没有和test-0.0.2分支代码合并,model层代码是不一样的。 然后执行
python manage.py makemigrations
python manage.py migrate
控制台输出了这些信息
migrations文件夹下的文件,生成了对应映射0003_auto_20220527_1116.py文件, 这个文件是基于0002_auto_20220527_1048.py。
从图上可以看出来,没错,这些存在test-0.0.2分支的字段被remove删除了,导致了数据库为啥会没了这些字段,并且对应的相关数据也没有了。 那究竟为什么出现这种情况呢?
其实 migrate 的作用是生成迁移和撤销迁移。 当执行 makemigrations 时,模型将被扫描并与当前包含在你的迁移文件中的版本进行比较,然后将写出一组新的迁移。 将模型生成迁移脚本,负责将模型修改打包进独立的迁移文件中。
也就是说第三次执行迁移的时候,会和第二次迁移做对比,对相应的内容进行检查,然后进行迁移生成或者迁移撤销。
3. 问题解决
既然问题找到了,那么怎么去避免呢? 大概有这几种解决方案:
- 代码合并,把model层都统一再执行数据迁移,这样就没啥问题了
- 如果代码没有合并,在部署自己代码的时候并不想影响到其他人的数据,那么可以使用这些命令
python manage.py makemigrations
python manage.py migrate --fake
可以看到控制台输出是这些
migrations文件夹下的文件生成的文件是这样子的。
但是到数据库看,原本的这些字段还是存在的,并没有跟着删除,那么这样子也达到了想要的效果。
当然也有一个问题,如果你真的想要删除某个数据字段,使用这个--fake 则会达不到效果,这种还是应该具体场景具体使用。
4. 其他
最后,放一些常见的命令吧。
# 全局迁移数据文件
python manage.py makemigrations
python manage.py migrate
# 迁移某个app_name的数据文件
python manage.py makemigrations app_name
python manage.py migrate app_name
# 合并冲突
python manage.py makemigrations --merge
# 将目标的迁移操作标记为已应用,不实际运行SQL来更改数据库结构
python manage.py migrate --fake
# 数据库表存在,则跳过初始迁移
python manage.py migrate --fake-initial