django默认不推荐uuid主键因其索引体积大、写入性能低、丢失时序性;仅在确需隐私保护或分布式场景才应替换,且须正确声明default=uuid.uuid4(非调用)并设editable=false。

为什么 Django 默认不推荐用 UUID 作为主键
Django 的 AutoField(即默认的 id)是递增整数,简单高效,但暴露 ID 会带来信息泄露风险——比如用户能猜出注册人数、订单总量,甚至通过 ID 爆破访问他人数据。UUID 能缓解这个问题,但代价是:索引体积变大、写入性能略低、排序失去自然时序性。所以它不是“更安全就无脑换”,而是“有明确隐私或分布式需求时才值得换”。
models.py 中正确声明 UUID 主键的写法
必须显式继承 models.Model 并覆盖 id 字段,不能只加一个额外字段。常见错误是漏掉 primary_key=True 或误用 default=uuid4(没加括号)。
正确写法:
import uuid from django.db import models <p>class MyModel(models.Model): id = models.UUIDField( primary_key=True, default=uuid.uuid4, # 注意:这里不是 uuid4(),是函数对象本身 editable=False ) name = models.CharField(max_length=100) </p>
-
default=uuid.uuid4:传函数引用,每次新建实例时自动调用生成新值 -
editable=False:防止 Django Admin 或表单意外修改该字段 - 不要用
default=uuid.uuid4():那样会在模块加载时就生成一个固定 UUID,所有实例共享同一个 ID
迁移时遇到 “non-default value” 错误怎么办
如果模型已有数据,直接加 UUIDField 主键会触发 NOT NULL constraint failed 或提示 “You are trying to add a non-nullable field without a default”。Django 不允许对已有表新增非空主键。
解决路径只有两条:
- 清空表重来(开发环境可行):
python manage.py migrate --fake-initial配合删库重迁 - 生产环境必须保留旧数据:先添加普通 UUID 字段 → 批量填充 → 再通过多步迁移切换主键(需手动编辑迁移文件,把旧
id字段设为db_column='old_id',再删之) - 更稳妥的做法:不替换主键,改用
unique=True的UUIDField作为业务主键(如public_id),保留id作内部关联用
PostgreSQL 和 MySQL 对 UUID 的实际影响差异
PostgreSQL 原生支持 UUID 类型,Django 会映射为 UUID,索引效率较高;MySQL 没有原生 UUID 类型,Django 默认用 CHAR(32) 存十六进制字符串(如 'a1b2c3d4...'),体积翻倍且无法利用二进制排序优势。
优化建议:
- MySQL 下可手动指定
db_type='BINARY(16)'并用uuid.uuid4().bytes存储,但需自定义to_python和get_prep_value - 查询时别用
str(uuid4())直接拼 SQL,应始终走 ORM 或参数化查询,避免大小写/连字符处理错误 - 外键关联 UUID 字段时,务必确认关联字段类型一致,否则 MySQL 可能静默转成字符串导致 JOIN 失效
真正麻烦的从来不是生成 UUID,而是后续所有依赖 ID 的地方——API 返回、前端缓存键、日志追踪、数据库备份还原……都得同步适配。别只盯着 default=uuid.uuid4 这一行代码。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











