IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。
大家好,我是IT策士。上一节我们完成了电商项目的需求分析,并把 10 张核心表写进了模型代码,成功在数据库里"落了户"。但模型只是建出来还不够,实际开发中你会频繁遇到这些场景:修改字段类型、加索引、调整关联关系,甚至还可能要做数据迁移------把老数据"搬"到新结构中。这些操作如果不理解底层原理,早晚会踩坑。
今天我们就来一次 Django 模型进阶与数据迁移的深度解析,帮你彻底搞懂 ORM 的高级用法,并掌握迁移系统的正确打开方式。
一、上节回顾与今天目标
上节回顾:
-
梳理了电商实体关系,编写了 users、products、cart、orders、payment 五个 app 的模型代码;
-
配置了自定义用户模型
AUTH_USER_MODEL = 'users.User'; -
执行了
makemigrations与migrate,把所有表建在了 SQLite 中。
今天目标:
-
深入 Django 模型字段的高级选项(
on_delete、related_name、索引、元选项等); -
理解 Django 迁移系统的工作原理;
-
实战:给已有模型添加索引、修改字段属性、创建数据迁移;
-
掌握迁移回滚、查看 SQL、合并迁移等常用操作。
二、模型进阶------那些你该掌握的细节
2.1 on_delete 的六种行为
外键字段都必须指定 on_delete。它决定了 被关联对象被删除时,当前对象的行为。六种取值如下:
在我们的项目中:
-
Order.sku使用了PROTECT,意思是商品在被订单引用时不允许删除,防止历史订单出现"幽灵商品"。 -
CartItem.sku使用了CASCADE,商品删除,购物车里对应的条目也应该消失。 -
Order.user使用PROTECT,用户不能在有订单的情况下被直接删除。
不同的业务场景需要不同的删除策略,这是模型设计的一个关键细节。
2.2 related_name 与反向查询
ForeignKey 会默认创建一个反向关系,名字是 模型名小写_set,例如 user.order_set.all()。为了语义更清晰,我们通常会自定义 related_name。
在我们的 Order 模型中:
bash
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
related_name='orders', # 自定义反向名
)
这样就能用 user.orders.all() 直接获取该用户的所有订单。如果你不想创建反向关系,可以设置 related_name='+'。
2.3 模型元选项 Meta
每个模型都可以通过内部类 Meta 配置元数据,常用的有:
-
db_table:自定义表名,我们统一用了tb_前缀。 -
verbose_name / verbose_name_plural:可读名称,Admin 后台会用到。 -
ordering:默认排序,如['-create_time']。 -
unique_together:联合唯一约束。我们在购物车模型中用了('user', 'sku')防止重复添加。 -
indexes:声明数据库索引(后面详解)。
2.4 字段属性详解
挑几个项目中重要且容易忽略的字段属性说一说:
-
decimal_places:DecimalField的精度至关重要。价格我们用max_digits=10, decimal_places=2,表示最大 10 位数,其中 2 位小数,能表示到 99,999,999.99。 -
auto_now_addvsauto_now:create_time用auto_now_add(新增时自动取值),update_time用auto_now(每次保存都自动更新)。 -
null=Truevsblank=True:前者是数据库层面的允许 NULL,后者是表单验证层面的允许为空。CharField一般不要用null=True,Django 推荐用空字符串代替。 -
JSONField:Django 3.1 起原生支持 JSON 字段(除 SQLite 外需要数据库支持)。我们用 JSON 存储商品规格快照和地址快照,灵活方便。
三、模型优化------给已有表加索引和默认值
索引能极大提升查询效率。电商系统里哪些字段经常作为查询条件?order_no(订单号)、user(用户外键)、create_time(时间范围查询)等。我们来给它们加上索引。
3.1 给订单表添加索引
编辑 apps/orders/models.py,在 Order 类的 Meta 中添加 indexes:
bash
class Order(models.Model):
# ... 字段省略 ...
class Meta:
db_table = 'tb_order'
verbose_name = '订单'
verbose_name_plural = verbose_name
ordering = ['-create_time']
indexes = [
models.Index(fields=['user', '-create_time'], name='idx_user_time'),
models.Index(fields=['status'], name='idx_status'),
]
我们创建了两个索引:
-
联合索引
idx_user_time:用户 ID 加创建时间降序,加速"某用户的订单列表"查询。 -
单列索引
idx_status:加速按状态筛选订单。
3.2 给 SKU 表添加索引
编辑 apps/products/models.py,在 SKU 的 Meta 里加上:
bash
class SKU(models.Model):
# ... 字段省略 ...
class Meta:
db_table = 'tb_sku'
verbose_name = 'SKU'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['spu', 'is_active'], name='idx_spu_active'),
]
联合索引 idx_spu_active 用于加速"查询某个 SPU 下所有上架 SKU"。
3.3 给商品 name 字段增加长度上限优化
SKU.name 目前长度 200,对于某些长规格名的商品可能不够,我们扩展到 500,同时加上数据库级别的默认空字符串(避免 NULL):
bash
name = models.CharField(
max_length=500,
default='',
verbose_name='SKU 名称'
)
四、数据迁移------从原理到实战
4.1 Django 迁移系统是如何工作的?
每次执行 makemigrations,Django 会扫描所有已注册 app 的 models.py,与上一次迁移后的状态做对比,生成一个迁移文件 (存在各 app 的 migrations/ 目录下)。迁移文件里有两个关键部分:
-
state_operations:描述模型"应该长什么样"。 -
database_operations:描述要在数据库上执行的操作(ALTER TABLE、CREATE INDEX 等)。
执行 migrate 时,Django 会根据迁移文件里的操作,翻译成对应的数据库 DDL 语句。
4.2 生成新的迁移
修改完模型后,运行:
bash
python manage.py makemigrations
控制台输出:
bash
Migrations for 'orders':
apps/orders/migrations/0002_auto_20260519_1430.py
- Create index idx_user_time on field(s) user, -create_time of model order
- Create index idx_status on field(s) status of model order
Migrations for 'products':
apps/products/migrations/0002_alter_sku_name_add_index.py
- Alter field name on sku
- Create index idx_spu_active on field(s) spu, is_active of model sku
如果你对迁移文件名不满意,可以用 --name 参数自定义:
bash
python manage.py makemigrations orders --name add_order_indexes
4.3 查看迁移对应的 SQL(很实用!)
在执行迁移前,我们最好先看一眼它会在数据库里执行哪些 SQL。使用 sqlmigrate 命令:
bash
python manage.py sqlmigrate orders 0002
输出示例(SQLite 版):
bash
BEGIN;
--
-- Create index idx_user_time on field(s) user, -create_time of model order
--
CREATE INDEX "idx_user_time" ON "tb_order" ("user_id", "create_time" DESC);
--
-- Create index idx_status on field(s) status of model order
--
CREATE INDEX "idx_status" ON "tb_order" ("status");
COMMIT;
换成 MySQL 或 PostgreSQL,生成的 DDL 会相应变化。这个命令可以让你在动手之前"审阅"SQL,避免意外。
4.4 执行迁移
确认无误后,执行:
控制台输出:
bash
Operations to perform:
Apply all migrations: orders, products
Running migrations:
Applying orders.0002_add_order_indexes... OK
Applying products.0002_alter_sku_name_add_index... OK
这时索引已经建立在数据库里了。可以用 dbshell 进入 SQLite,输入 .indexes tb_order 查看索引列表。
五、迁移的"后悔药"------回滚与恢复
5.1 撤销最近一次迁移
假设刚才的迁移有问题,我们可以退回到指定状态:
bash
python manage.py migrate orders 0001
这个命令会将 orders app 的迁移状态退回到 0001,对应的表结构也会回退(索引会被删除)。
执行后会提示:
bash
Operations to perform:
Target specific migration: 0001_initial, from orders
Running migrations:
Rendering model states... DONE
Unapplying orders.0002_add_order_indexes... OK
5.2 撤销所有迁移(慎用)
如果想回到零迁移状态(表清空),可以指定 zero:
bash
python manage.py migrate orders zero
但在生产环境千万别这么做,除非你真的要删除整个 app 的数据表。
六、高级话题:数据迁移(Data Migration)
有时候你不仅要改表结构,还需要搬移或转换数据。比如我们想在 tb_users 表里给现有用户批量生成一个默认的"手机号"字段,让其不再是 NULL。
6.1 创建空迁移作为数据迁移的壳
bash
python manage.py makemigrations users --empty --name populate_user_phone
这会生成一个空的迁移文件 apps/users/migrations/0002_populate_user_phone.py。我们打开它,在 operations 列表里写上数据迁移逻辑:
bash
from django.db import migrations
def populate_phone_default(apps, schema_editor):
User = apps.get_model('users', 'User')
# 将 phone 为空的用户设置一个临时占位手机号,实际中可根据业务逻辑处理
for user in User.objects.filter(phone__isnull=True):
user.phone = f"1000000{user.id:04d}" # 例如 10000000001
user.save(update_fields=['phone'])
def reverse_phone(apps, schema_editor):
# 回滚逻辑:将生成的手机号置空
User = apps.get_model('users', 'User')
User.objects.filter(phone__startswith='1000000').update(phone=None)
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.RunPython(populate_phone_default, reverse_phone),
]
然后执行迁移:
这样既修改了表结构,也填充了历史数据。
七、常用迁移命令速查
八、总结与下集预告
今天我们把 Django 模型从"会用"提升到了"懂得内部机制"的水平:
-
吃透了
on_delete、related_name、Meta选项的用法; -
给订单和商品表添加了关键索引,优化查询;
-
深入理解了 Django 迁移的原理,学会了生成、查看、回滚迁移;
-
初次体验了数据迁移,处理了历史数据问题。
有了扎实的模型基础,下一步就该让数据"活"起来了。第 4 天 ,我们将迎来 Django 最让人兴奋的功能之一------Admin 后台管理。我会教你如何在 10 分钟内搭出一个能管理用户、商品、订单的后台,并实现一些高级定制,比如列表过滤、搜索、自定义表单......让运营人员也能直接上手。
想了解更多还可以去其它平台搜索「IT策士」,一起升级 IT 思维 !
本文为《Django 从 0 到 1 打造完整电商平台》系列第 3 篇,作者:IT策士,未经授权禁止转载。