应保留autofield主键,另设public_id=uuidfield(unique=true, default=uuid.uuid4, editable=false)作对外标识,避免id枚举且规避迁移、性能与外键风险;路由用校验格式,视图中显式查询。

UUID 用在 URL 路径里,不是为了替代主键,而是为隔离业务暴露面和数据存储层——你完全可以保留 AutoField 主键,只把 UUID 当作对外可见的标识符。这样既避免 ID 枚举、又不破坏 Django 内部关联逻辑和迁移稳定性。
为什么不能直接用 UUID 替换主键?
直接改 id = models.UUIDField(primary_key=True, ...) 看似一劳永逸,但实际踩坑密集:
- 已有数据表无法直接加非空主键,
migrate会报NOT NULL constraint failed - MySQL 默认存成
CHAR(36),索引体积翻倍、B-tree 插入随机、性能明显下降 - 外键字段(如
ForeignKey)必须同步改成UUIDField,否则关联失效 - Admin 和 ModelForm 默认允许编辑主键,
editable=False忘写就会导致数据错乱
用 public_id 字段更稳妥
推荐做法是:保留 id 自增主键,额外加一个业务标识字段:
class Article(models.Model):
id = models.AutoField(primary_key=True) # 内部关联用
public_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
title = models.CharField(max_length=200)
好处很实在:
- 迁移零风险:新增字段可设
null=True先上线,再批量填充 - 路由干净:
path('article/<public_id>/', views.article_detail)</public_id>自动校验格式 - API 输出可控:序列化时只暴露
public_id,id不透出 - 数据库无压力:MySQL 仍用整型主键索引,
public_id加db_index=True单独优化查询
<pk></pk> 路由转换器真能防手输错误?
能,但仅限格式层面。Django 的 <pk></pk> 会自动调用 UUID() 构造器,所以:
- 接受带连字符、小写(
a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8) - 拒绝大写、缺连字符、长度不对(如
A1B2C3D4E5F67890G1H2I3J4K5L6M7N8或a1b2c3d4e5f67890g1h2i3j4k5l6m7n8) - 查不到记录时直接 404,不会因字符串截断或类型转换错误导致误查
但注意:它不校验该 UUID 是否真实存在于数据库——那是视图逻辑该干的事。
Slug 和 UUID 该怎么选?
两者解决的问题不同,别混用:
-
SlugField适合 SEO 友好、用户可读的路径(如/blog/my-first-post/),但需处理重复、编码、长度限制 -
UUID适合机器间通信、API 标识、防遍历场景(如/api/v1/order/a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8/),完全规避语义冲突 - 真要兼顾可读性+唯一性,可用
slug = models.SlugField(unique=True, default=generate_random_slug),但生成逻辑必须保证全局唯一
最常被忽略的一点:无论选哪个,都得在视图里显式用 .get(public_id=...) 或 .filter(slug=...) 查询,别依赖 pk 参数自动解析——因为 pk 默认还是指 id 字段。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











