Python
模型、ORM 与数据库迁移
发布于 2026年7月22日
模型、ORM 与数据库迁移
模型决定了项目的数据结构,也是大多数 Django 业务的核心。本篇将从字段定义、关联关系、查询集一直讲到迁移和事务。
一、模型与数据库表
一个模型类通常对应一张表,一个字段对应一列,一个模型实例对应一行数据。
from django.conf import settings
from django.db import models
class Message(models.Model):
class Status(models.TextChoices):
DRAFT = "draft", "草稿"
PUBLISHED = "published", "已发布"
ARCHIVED = "archived", "已归档"
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="messages",
)
name = models.CharField("姓名", max_length=50)
content = models.TextField("内容")
status = models.CharField(
"状态",
max_length=20,
choices=Status.choices,
default=Status.DRAFT,
db_index=True,
)
created_at = models.DateTimeField("创建时间", auto_now_add=True)
updated_at = models.DateTimeField("更新时间", auto_now=True)
class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["owner", "status"]),
]
def __str__(self):
return self.name
使用 settings.AUTH_USER_MODEL,不要在模型字段中直接写死 User。
二、常用字段与参数
常见字段:
CharField:短文本,必须提供max_length。TextField:长文本。IntegerField、DecimalField:整数和精确小数。BooleanField:布尔值。DateField、DateTimeField:日期和时间。EmailField、URLField:带基础格式校验的字符串。FileField、ImageField:文件路径,文件本身通常位于媒体存储。JSONField:结构不固定的 JSON 数据,不应替代清晰的关系建模。
常用参数:
null=True:数据库允许NULL。blank=True:表单校验允许为空。default:默认值,可传可调用对象,如default=timezone.now。unique=True:唯一约束。db_index=True:建立单字段索引。choices:限制可选值并提供显示文本。editable=False:默认不出现在 ModelForm 和后台编辑表单中。
字符串字段通常用空字符串表示“无值”,不要随意同时设置 null=True 和 blank=True。
三、模型关系
1. 一对多:ForeignKey
class Comment(models.Model):
message = models.ForeignKey(
Message,
on_delete=models.CASCADE,
related_name="comments",
)
content = models.TextField()
访问关系:
comment.message
message.comments.all()
on_delete 常用策略:
CASCADE:主对象删除时一起删除。PROTECT:存在引用时禁止删除。SET_NULL:设为空,字段必须允许null=True。SET_DEFAULT:设为默认值。
2. 多对多:ManyToManyField
class Tag(models.Model):
name = models.CharField(max_length=30, unique=True)
class Message(models.Model):
# 省略其他字段
tags = models.ManyToManyField(Tag, blank=True, related_name="messages")
3. 一对一:OneToOneField
适合用户扩展资料等“每个主对象最多一条扩展记录”的场景。
四、迁移的正确工作流
修改 models.py 后执行:
python manage.py makemigrations
python manage.py migrate
辅助命令:
python manage.py showmigrations
python manage.py sqlmigrate notes 0002
python manage.py migrate notes 0001
makemigrations:根据模型变化生成迁移文件。migrate:按依赖顺序执行迁移。- 迁移文件属于源代码,应提交版本控制。
- 已在共享环境执行过的迁移,不要随意修改或删除;应创建新迁移修正。
给已有表增加非空字段时,需提供合理默认值,或分阶段操作:先允许空值、回填数据、再改为非空。
五、数据迁移
结构迁移改变表,数据迁移负责批量修正已有数据。
from django.db import migrations
def publish_existing_messages(apps, schema_editor):
Message = apps.get_model("notes", "Message")
Message.objects.filter(status="").update(status="published")
class Migration(migrations.Migration):
dependencies = [("notes", "0002_message_status")]
operations = [
migrations.RunPython(
publish_existing_messages,
migrations.RunPython.noop,
),
]
数据迁移中应使用 apps.get_model() 取得历史模型,不能直接导入当前 models.py。
六、ORM 增删改查
进入交互环境:
python manage.py shell
from notes.models import Message
# 创建
message = Message.objects.create(
owner_id=1,
name="小林",
content="正在学习 Django",
)
# 查询
all_messages = Message.objects.all()
published = Message.objects.filter(status="published")
one = Message.objects.get(pk=1)
# 更新
message.status = Message.Status.PUBLISHED
message.save(update_fields=["status", "updated_at"])
# 批量更新
Message.objects.filter(status="draft").update(status="published")
# 删除
message.delete()
get() 找不到会抛出 DoesNotExist,找到多条会抛出 MultipleObjectsReturned。不确定是否存在时可用:
message = Message.objects.filter(pk=1).first()
七、查询条件与表达式
from django.db.models import Count, Q
Message.objects.filter(name__icontains="林")
Message.objects.filter(created_at__year=2026)
Message.objects.exclude(status="draft")
Message.objects.filter(Q(name__icontains="林") | Q(content__icontains="Django"))
Message.objects.annotate(comment_count=Count("comments"))
Message.objects.values("status").annotate(total=Count("id"))
常用查找后缀:exact、iexact、contains、icontains、in、gt、gte、lt、lte、isnull。
QuerySet 是惰性执行的。创建、过滤和排序时通常不会立刻查询数据库,迭代、切片、list()、len() 等操作才可能真正执行 SQL。
八、避免 N+1 查询
访问外键或多对多关系时,循环可能产生大量额外查询。
# ForeignKey / OneToOne
messages = Message.objects.select_related("owner")
# ManyToMany / 反向 ForeignKey
messages = Message.objects.prefetch_related("tags", "comments")
select_related()使用 SQL JOIN,适合单值关系。prefetch_related()使用额外查询后在 Python 中合并,适合集合关系。
九、事务
多个写操作必须全部成功或全部失败时使用事务:
from django.db import transaction
@transaction.atomic
def publish_message(message, log_model):
message.status = Message.Status.PUBLISHED
message.save(update_fields=["status"])
log_model.objects.create(message=message, action="publish")
不要在长事务中执行网络请求、发送邮件或运行耗时任务,以免长时间占用数据库锁。
十、常见问题
no such table:通常尚未执行migrate。NOT NULL constraint failed:新增非空字段时旧数据没有值。- 数据重复:只在代码里检查不够,关键规则应加数据库唯一约束。
- 查询很慢:先确认 SQL 与查询次数,再考虑索引,不能盲目给所有字段加索引。
- 金额误差:金额使用
DecimalField,不要用FloatField。
十一、本篇检查清单
- 能选择合适字段并区分
null与blank。 - 能设计一对一、一对多和多对多关系。
- 理解迁移文件为什么要纳入版本控制。
- 能使用过滤、聚合和事务。
- 能识别并处理 N+1 查询。
上一篇:URL 路由与视图 | 下一篇:模板、静态文件与媒体文件