Django中makemigrations和migrate命令如何同步数据库迁移?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1173个文字,预计阅读时间需要5分钟。
当你没有修改`models.py`文件时,执行`python manage.py makemigrations`命令,结果会显示没有检测到更改,但仍然会生成一个空迁移文件(例如`0002_auto_20240510_1523.py`)。这是因为模型类中可能写入了动态内容,例如使用`default=datetime.now()`而不是`default=datetime.now()`(注意括号)。Django每次运行`makemigrations`都会重新导入模型,如果默认值是函数调用(带括号),它每次都会看到不同的当前时间,因此认为字段发生了变化。
- 正确写法:
default=timezone.now(不带括号,传函数对象)或default=datetime.now(同理) - 错误写法:
default=datetime.now()或default=uuid4()(立即执行,每次导入都不同) - 另一个常见原因:修改了
Meta.ordering、verbose_name等不影响数据库结构的属性,Django 默认不忽略它们——可加--name手动跳过,或在settings.py中设MIGRATION_MODULES后统一管理
migrate 执行时报错 “Table already exists” 怎么办
典型错误信息:django.db.utils.ProgrammingError: relation "myapp_mymodel" already exists。这不是代码问题,而是 Django 的迁移记录表(django_migrations)和实际数据库状态不一致:表已经存在,但对应迁移没被标记为已执行。
- 先确认:查
SELECT * FROM django_migrations WHERE app = 'myapp' AND name = '0001_initial';,看有没有这条记录 - 如果没记录但表存在,说明你手动建过表,或者之前用
sqlmigrate+psql手动执行过 SQL —— 此时该用python manage.py migrate myapp 0001 --fake告诉 Django “这个迁移我假装执行过了” - 如果已有记录却还报错,可能是迁移文件被删过又重建,导致名字重复;此时要删掉新生成的迁移文件,再
makemigrations --empty myapp写个空迁移来对齐状态 - 切忌直接删数据库表重来,尤其在线上环境 ——
--fake是安全前提下的首选操作
如何让 migrate 跳过某个迁移文件
有时候你想临时绕过一个有问题的迁移(比如它依赖外部服务、或本地开发想快速回退),但又不想删文件(怕团队同步出错)。Django 本身不支持“跳过”,但有可控的替代路径。
- 用
python manage.py migrate myapp 0001指定回退到某一步,相当于“停在那”,之后再migrate就从那里继续 - 若想彻底忽略某次迁移(比如它只在测试环境有意义),可在迁移文件开头加判断:
from django.db import migrations def skip_if_not_production(apps, schema_editor): if not settings.DEBUG: # 实际逻辑放这里 pass <p>class Migration(migrations.Migration): dependencies = [...] operations = [ migrations.RunPython(skip_if_not_production, reverse_code=migrations.RunPython.noop), ]
- 注意:
RunPython的reverse_code必须显式指定,否则migrate --fake-reverse会失败
SQLite 和 PostgreSQL 在 migrate 上的关键差异
本地用 SQLite、线上用 PostgreSQL 是常见组合,但两者对迁移的支持力度不同,容易在部署时翻车。
- SQLite 不支持多数 DDL 变更:比如不能
ALTER COLUMN改字段类型、不能删字段、不能加非空约束(除非用db_column手动映射)。Django 遇到这类操作会静默降级为“重建表”,但数据可能丢失 —— 所以别在 SQLite 上信migrate的行为 - PostgreSQL 支持原子性迁移,
ALTER TABLE ... ADD COLUMN这类操作能原地执行;但要注意NOT NULL字段必须配default,否则报错cannot create NOT NULL column without default value - 跨数据库迁移前务必跑一次
python manage.py sqlmigrate myapp 0001,看看生成的 SQL 是否符合目标库语法,特别是JSONField、ArrayField这些特有字段
迁移不是“点一下就完事”的黑盒,它的核心其实是三件事:模型定义、迁移文件快照、数据库当前状态。哪一环脱节,都会在 migrate 那一刻暴露。最常被忽略的是——迁移文件一旦提交到 Git,就不再是“可随意编辑”的脚本,而是团队共享的事实来源。
本文共计1173个文字,预计阅读时间需要5分钟。
当你没有修改`models.py`文件时,执行`python manage.py makemigrations`命令,结果会显示没有检测到更改,但仍然会生成一个空迁移文件(例如`0002_auto_20240510_1523.py`)。这是因为模型类中可能写入了动态内容,例如使用`default=datetime.now()`而不是`default=datetime.now()`(注意括号)。Django每次运行`makemigrations`都会重新导入模型,如果默认值是函数调用(带括号),它每次都会看到不同的当前时间,因此认为字段发生了变化。
- 正确写法:
default=timezone.now(不带括号,传函数对象)或default=datetime.now(同理) - 错误写法:
default=datetime.now()或default=uuid4()(立即执行,每次导入都不同) - 另一个常见原因:修改了
Meta.ordering、verbose_name等不影响数据库结构的属性,Django 默认不忽略它们——可加--name手动跳过,或在settings.py中设MIGRATION_MODULES后统一管理
migrate 执行时报错 “Table already exists” 怎么办
典型错误信息:django.db.utils.ProgrammingError: relation "myapp_mymodel" already exists。这不是代码问题,而是 Django 的迁移记录表(django_migrations)和实际数据库状态不一致:表已经存在,但对应迁移没被标记为已执行。
- 先确认:查
SELECT * FROM django_migrations WHERE app = 'myapp' AND name = '0001_initial';,看有没有这条记录 - 如果没记录但表存在,说明你手动建过表,或者之前用
sqlmigrate+psql手动执行过 SQL —— 此时该用python manage.py migrate myapp 0001 --fake告诉 Django “这个迁移我假装执行过了” - 如果已有记录却还报错,可能是迁移文件被删过又重建,导致名字重复;此时要删掉新生成的迁移文件,再
makemigrations --empty myapp写个空迁移来对齐状态 - 切忌直接删数据库表重来,尤其在线上环境 ——
--fake是安全前提下的首选操作
如何让 migrate 跳过某个迁移文件
有时候你想临时绕过一个有问题的迁移(比如它依赖外部服务、或本地开发想快速回退),但又不想删文件(怕团队同步出错)。Django 本身不支持“跳过”,但有可控的替代路径。
- 用
python manage.py migrate myapp 0001指定回退到某一步,相当于“停在那”,之后再migrate就从那里继续 - 若想彻底忽略某次迁移(比如它只在测试环境有意义),可在迁移文件开头加判断:
from django.db import migrations def skip_if_not_production(apps, schema_editor): if not settings.DEBUG: # 实际逻辑放这里 pass <p>class Migration(migrations.Migration): dependencies = [...] operations = [ migrations.RunPython(skip_if_not_production, reverse_code=migrations.RunPython.noop), ]
- 注意:
RunPython的reverse_code必须显式指定,否则migrate --fake-reverse会失败
SQLite 和 PostgreSQL 在 migrate 上的关键差异
本地用 SQLite、线上用 PostgreSQL 是常见组合,但两者对迁移的支持力度不同,容易在部署时翻车。
- SQLite 不支持多数 DDL 变更:比如不能
ALTER COLUMN改字段类型、不能删字段、不能加非空约束(除非用db_column手动映射)。Django 遇到这类操作会静默降级为“重建表”,但数据可能丢失 —— 所以别在 SQLite 上信migrate的行为 - PostgreSQL 支持原子性迁移,
ALTER TABLE ... ADD COLUMN这类操作能原地执行;但要注意NOT NULL字段必须配default,否则报错cannot create NOT NULL column without default value - 跨数据库迁移前务必跑一次
python manage.py sqlmigrate myapp 0001,看看生成的 SQL 是否符合目标库语法,特别是JSONField、ArrayField这些特有字段
迁移不是“点一下就完事”的黑盒,它的核心其实是三件事:模型定义、迁移文件快照、数据库当前状态。哪一环脱节,都会在 migrate 那一刻暴露。最常被忽略的是——迁移文件一旦提交到 Git,就不再是“可随意编辑”的脚本,而是团队共享的事实来源。

