Django: MySQL 导出导入现有数据库 及 models.py新增模型类包含外键ForeignKey时,makemigrations migrate 迁移报错 django.db.utils.OperationalError: 3780 ... incompatible
1. 概述:
MySQL 导出导入现有数据库 及 models.py新增模型类包含外键ForeignKey时,makemigrations migrate 迁移报错
2. 相关报错:
django.db.utils.OperationalError: (3780, "Referencing column 'customer_id' and referenced column 'id' in foreign key constraint 'TS_user_customer_id_545183b3_fk_customer_info_id' are incompatible.")
3. 报错原因:
django.db.utils.OperationalError: 3780 引用列和被引用列不兼容
如果没有手动修改数据库的主字段,则很可能是创建数据库的Django 版本不一样,使用ForeignKey 的时候, primary_key 有的版本使用BigAutoField 而有些使用AutoField ,而在某些DB 版本中,这两个会在create table 时分别作为bigint 跟int,而这个就是产生这个错误的主因。
ForeignKey 在实际使用时(假设叫做xxx),会在table 内建立一个xxx_id ,而这个会去参照ForeignKey 参照的model 的primary_key 。 而如果两边的Django 版本不同的话就会3780报错出来。
4. 解决办法
查看app下的apps.py
from django.apps import AppConfig class GoodsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' # AutoField为bigint name = 'goods'
发现默认的表的id字段为bigint
现有
表一:
class GoodsCategory(models.Model): name = models.CharField(max_length=10) class Meta: db_table = 'tb_goods_category'
若增加
表二:
class GoodsVisitCount(models.Model): category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE) class Meta: db_table = 'tb_goods_visit'
则 makemigrations migrate 迁移时报错。
检查数据库报错的表:
MySQL root@(none):xxx> show create table tb_goods_category; CREATE TABLE `tb_goods_category` ( `id` int NOT NULL AUTO_INCREMENT, ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
发现报错的表id字段为int,可能是旧版本Django创建的。
现在新增表中ForeignKey字段,在创建时因为默认为bigint,与要关联的表id字段int不同,所以提示引用列和被引用列不兼容
解决办法为手动修改原有的表id字段为bigint,重新migrate即可。
若有使用Django makemigrations migrate建表之后,导入MySQL备份文件,提示incompatible的,
手动修改MySQL备份文件中报错的字段(引用列或被引用列)类型,使之与Django创建的表一致即可。
参考:
1. [Django] 解决django.db.utils.OperationalError: 3780
django.db.utils.OperationalError: 3780 引用列和被引用列不兼容
这个主因其实是我devel 跟production 的Django 版本不一样,当然有很多因素让我选择不同步(因为连作业系统都不一样了),而这是一个很常见的Error.
这个常见的因素是使用ForeignKey 的时候, primary_key 有的版本或作业系统使用BigAutoField 而有些使用AutoField ,而在某些DB 版本中,这两个会在create table 时分别实作为bigint 跟int,而这个就是产生这个Error 的主因。
ForeignKey 在实际使用时(假设叫做xxx),会在table 内建立一个xxx_id ,而这个会去参照ForeignKey 参照的model 的primary_key 。 而如果两边的Django 版本不同的话就很容易raise 3780 出来。
解法其实很简单,首先找出出错的migrations ,执行
python3 manage.py sqlmigrate <APP Name> <migration>
这个时候会出现一堆SQL 指令,然后再执行
python3 manage.py dbshell
开始执行sqlmigrate 得到的指令看是哪一行出错,找出出错的一行去alter 成正确的就行了。
例如说假如xxx 的id 是int auto_increment not null ,但是ForeignKey 产生出来的xxx_id 却是bigint default null 的时候,那就执行:
alter table <table_name> modify column xxx_id int Default NULL;
就可以了。
然后如果出错的栏位后面还有其他SQL commands 记得执行完。
最后因为其实已经在database level 执行完了,所以可以直接把这个有问题的migration fake 掉。
python3 manage.py sqlmigrate <APP Name> <migration> --fake
好了,暴力解完。 虽然没有解决根本问题但是至少可以不用被这个问题挡住开发。
2. Django解决ForeignKey引用外键时类型不匹配问题(3780)
方法一:
1.报错的代码:
File "F:\pychrom_python\web\django\venv\meiduo_project_venv\lib\site-packages\pymysql\err.py", line 143, in raise_mysql_exception
raise errorclass(errno, errval)
pymysql.err.OperationalError: (3780, "Referencing column 'xxx' and referenced column 'xxx' in foreign key constraint 'xxxx' are incompatible.")
2.报错的原因:
在一个模型中使用field_name=models.ForeignKey(‘models_name’)去创建一个外键字段的时候,在迁移模型的时候,django会在数据库表中创建一个field_name_id的字段,而该字段的类型与引用其他表中的主键的类型不匹配,所有在迁移的时候django会raise 3780出来.
3.查看当前迁移文件时生成的sql命令
使用终端中使用python manage.py sqlmigrate <app_name> <migration>
查看当前迁移文件所产生的sql语句
当前迁移创建的外键字段类型为bigint,而我这里引用表中的主键的类型为int。
4.解决方法:
找到当前的迁移文件
class Migration(migrations.Migration):
dependencies = [
('goods', '0003_rename_default_image_url_sku_default_image'),
]
operations = [
migrations.CreateModel(
name='GoodsVisitCount',
fields=[
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True, verbose_name='主键')),
('count', models.IntegerField(default=0, verbose_name='访问量')),
('date', models.DateField(auto_now_add=True, verbose_name='统计日期')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.goodscategory', verbose_name='商品分类')),
],
options={
'verbose_name': '统计分类商品访问量',
'verbose_name_plural': '统计分类商品访问量',
'db_table': 'tb_goods_visit',
},
),
]
这里是通过migrations.CreateModel方法创建表,而django通过models.ForeignKey()创建外键,默认的类型为bigint,删除当前迁移文件migrations.CreateModel()方法的代码,改为通过migrations.runsql方法的sql语句的形式创建表,如下:
class Migration(migrations.Migration):
dependencies = [
('goods', '0003_rename_default_image_url_sku_default_image'),
]
operations = [
migrations.RunSQL(
"""
CREATE TABLE tb_goods_visit(
create_time datetime(6) NOT NULL,
update_time datetime(6) NOT NULL,
id integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
count integer NOT NULL,
date date NOT NULL,
category_id int NOT NULL,
CONSTRAINT tb_goods_visit_category_id_b3e36237_fk_tb_goods_category_id FOREIGN KEY(category_id) REFERENCES tb_goods_category(id)
);
"""
)
]
执行python manage.py migrate 命令
(meiduo_project_venv) F:\pychrom_python\web\django\meiduo_project\meiduo>python manage.py migrate
Operations to perform:
Apply all migrations: admin, areas, auth, contents, contenttypes, goods, oauth, sessions, users
Running migrations:
Applying goods.0004_goodsvisitcount... OK
5.测试:
>>>(meiduo_project_venv) F:\pychrom_python\web\django\meiduo_project\meiduo>python manage.py shell
>>> from goods.models import GoodsVisitCount
>>> a=GoodsVisitCount.objects.get(id=1)
>>> a.category
<GoodsCategory: 相机>
>>> a.category.name
'相机'
成功关联表查询出记录
**
方法二:
**
通过更改配置文件下的DEFAULT_AUTO_FIELD配置
对于我上面的出现的问题,可以删除子应用下对应的迁移文件,删除数据库迁移表中对应的迁移记录,重新生成迁
移文件,重新迁移表到数据库即可,并且配置更改为:DEFAULT_AUTO_FIELD=django.db.models.IntegerField
相关更多说明可以查看 https://docs.djangoproject.com/zh-hans/4.0/ref/settings/ django官方文档中DEFAULT_AUTO_FIELD的配置说明。