Django月度测验应用用户答题响应存储方案设计咨询
JSON文件存储答题记录方案的缺陷
- 一致性无保障:答题提交过程中如果出现进程中断、服务宕机,JSON文件很容易出现半写入的格式损坏,单文件内存储的多月份答题记录可能全部丢失,无法像数据库一样通过事务机制保证写入操作要么全部成功、要么全部回滚。
- 并发写入易冲突:如果用户同时打开多个答题页面、或后台任务同时更新用户答题状态,多进程同时操作同一个JSON文件会直接出现内容覆盖,最终留存的答题记录错乱,若自行实现文件锁机制,会带来额外的性能开销和逻辑漏洞风险。
- 查询统计效率极低:做错题率统计、用户得分计算、单场测验参与情况汇总这类操作时,无法直接通过数据库筛选,需要遍历读取所有用户的JSON文件逐个解析,用户量上涨后磁盘IO开销会直接拖垮服务,也无法通过索引实现快速查询。
- 数据校验能力缺失:JSON文件内存储的题目ID、选项ID无法和数据库内的题目、选项数据做联动校验,一旦题目被删除、调整,文件内会留存大量无效脏数据,后续清理、核对的成本极高。
- 运维复杂度高:JSON文件存储在本地磁盘,多节点部署、服务迁移、数据备份时需要单独同步文件资源,远不如数据库天然支持主从同步、统一备份的机制可靠,很容易出现文件漏同步导致的数据丢失。
可落地的数据库存储优化方案
你目前编写的UserQuizAnswer模型已经符合关系型存储的核心思路,只需要修正现有模型的逻辑bug、补全缺失的关联关系即可,完全可以满足全量答题记录留存的需求,后续导出JSON做数据分析也比读文件更灵活:
- 模型层面的调整点
- 给
Answer模型添加关联题目的外键,明确每个选项对应的所属题目 - 给
UserQuizAnswer添加关联测验的外键,不需要通过题目间接关联所属测验,方便快速筛选单场测验的所有答题记录 - 删除
Question模型中冗余的month、year字段,这类信息可以通过关联的Quiz外键直接获取,避免重复存储带来的数据不一致 - 修正
Quiz模型save方法的逻辑错误,现有判断逻辑在quiz_name为空时拼接内容的写法完全无效 - 修正
UserQuizAnswer的字段拼写错误(selcted_index应为selected_index)
- 给
- 优化后的参考模型代码
from django.db import models from django.contrib.auth.models import User import uuid class Quiz(models.Model): id = models.AutoField(primary_key=True) quiz_name = models.CharField(max_length=500) month = models.CharField(max_length=250, null=True, blank=True) year = models.CharField(max_length=250, null=True, blank=True) def __str__(self): return f"{self.quiz_name}-{self.month}-{self.year}" def save(self, *args, **kwargs): # 修正原save方法的逻辑错误:自动生成测验名称时判断名称是否未设置 if not self.quiz_name: self.quiz_name = f"月度测验-{self.month}-{self.year}" super().save(*args, **kwargs) class Question(models.Model): id = models.AutoField(primary_key=True) question = models.CharField(max_length=500) quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE) # 删除冗余的month、year字段,通过quiz关联获取即可 def __str__(self): return f"{self.question}-{self.quiz.month}-{self.quiz.year}" class Answer(models.Model): id = models.AutoField(primary_key=True) answer = models.CharField(max_length=500) score = models.IntegerField() # 新增外键关联所属题目 question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="options") # 新增选项索引字段,和用户选择的索引对应 option_index = models.IntegerField() def __str__(self): return self.answer class UserQuizAnswer(models.Model): options = ( (0, 0), (1, 1), (2, 2), (3, 3) ) id = models.AutoField(primary_key=True) question = models.ForeignKey(Question, on_delete=models.CASCADE) # 新增外键关联所属测验,方便快速筛选单场测验记录 quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) # 修正原字段拼写错误 selected_index = models.IntegerField(choices=options) # 可选:新增答题时间、是否正确等冗余字段,减少后续统计时的关联查询 submit_time = models.DateTimeField(auto_now_add=True) is_correct = models.BooleanField(default=False) class Meta: # 添加联合索引,大幅提升查询效率 indexes = [ models.Index(fields=['user', 'quiz']), models.Index(fields=['quiz', 'question']), ] def __str__(self): return f"{self.user.username}-{str(uuid.uuid4())[:10]}" class StudentFile(models.Model): id = models.AutoField(primary_key=True) name = models.TextField(null=True, blank=True) student = models.ForeignKey(User, on_delete=models.CASCADE) student_netid = models.IntegerField(null=True, blank=True) student_email = models.EmailField(null=True, blank=True) file = models.FileField(upload_to="Student/Files/", null=True, blank=True) month = models.CharField(max_length=250, null=True, blank=True) year = models.CharField(max_length=250, null=True, blank=True)
- 关于JSON导出的说明
用数据库存储完全不影响后续的数据分析需求,只要用Django ORM查询出对应范围的答题记录,调用序列化模块就能直接导出成结构化JSON数据,支持按需筛选字段、过滤范围,比读取零散的磁盘文件灵活度高很多。
内容的提问来源于stack exchange,提问作者Sovit Nayak
相关产品推荐
相关产品推荐

