
当 django 模型使用自定义 default 函数(如 generate_unique_id)初始化主键时,若该函数内部误用 objects.create() 触发数据库写入,将导致无限递归或阻塞——尤其在 sqlite 等轻量级数据库中表现为主进程“假性冻结”。根本原因在于默认值生成逻辑意外触发了模型实例保存流程。
这个问题看似神秘:Shell 中执行 AllowedUser(...) 无响应,DRF 中调用 serializer.save() 后静默中断,且无异常抛出。实际上,罪魁祸首并非模型定义本身,而是其依赖的 default 函数——generate_unique_id()。
原始错误版本的关键缺陷在于:
def generate_unique_id():
while True:
new_id = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8)).upper()
# ❌ 错误:使用 create() 尝试插入数据,而此时模型实例尚未构建完成
# 这会触发 save() → 调用 default → 再次 create → 无限循环/死锁
AllowedUser.objects.create(id=new_id) # 缺少必要字段(如 name、place),但更严重的是逻辑错误
return new_id为什么会导致“冻结”?
- SQLite 默认使用 serialized transaction mode,对写操作加全局锁;
- objects.create() 尝试向数据库插入一条记录,但当前事务(即正在构造 AllowedUser 实例的上下文)尚未提交,甚至尚未开始;
- 更关键的是:create() 会强制调用 save(),而 save() 又会再次尝试计算 id 的 default 值 → 形成隐式递归调用链;
- 在 SQLite 上,这种未完成的写操作极易因锁竞争或事务挂起表现为“长时间等待”,而非报错。
✅ 正确做法是:仅查询(READ),绝不写入(WRITE)。校验 ID 唯一性必须通过 filter().exists() 或 count(),而非 create():
import random
import string
from django.db import models
def generate_unique_id():
max_attempts = 100
for _ in range(max_attempts):
new_id = ''.join(random.choices(string.ascii_letters + string.digits, k=8)).upper()
# ✅ 安全:只读查询,不触发 save 或事务写入
if not AllowedUser.objects.filter(id=new_id).exists():
return new_id
raise RuntimeError("Failed to generate unique ID after {} attempts".format(max_attempts))? 额外建议与最佳实践:
- 避免在 default 中进行 I/O 或 DB 查询:虽此处必要,但应加入重试上限(如 max_attempts),防止极端情况下无限循环;
- 优先使用数据库原生唯一约束:SQLite 支持 UNIQUE 索引,配合 try/except IntegrityError 更健壮;
- DRF 序列化器调试技巧:在 serializer.save() 前添加 print(serializer.is_valid(), serializer.errors),确认是否卡在验证阶段;
- 启用 Django SQL 日志:在 settings.py 中配置 LOGGING,可直观看到卡在哪个 SQL 语句上,快速定位阻塞点。
总结:Django 模型“冻结”往往不是框架缺陷,而是默认值逻辑无意中撬动了持久化链条。保持 default 函数的纯函数特性(无副作用、无 DB 写入、无状态依赖),是避免此类隐蔽陷阱的核心原则。










