Django中hashlib持续生成重复十六进制摘要问题
问题根源分析
你遇到的这个重复hash问题,核心原因是新增图书时,生成hash的种子并不唯一,具体来说:
当你创建新的Book对象时,在调用super().save()之前,self.pk还没有被数据库赋值(因为对象还没写入数据库),所以str(self.pk)的结果是字符串"None"。而created_at是auto_now_add=True,它的值是对象第一次被保存时的时间,如果两个图书在同一秒(甚至同一微秒,取决于数据库时间精度)被创建,created_at的值会完全相同。
这就导致两个不同的图书生成的string_seed是一模一样的("时间字符串" + "None"),最终得到的hash自然重复,触发了唯一约束错误。
另外你的save方法还有个小问题:没有传递*args和**kwargs给父类的save方法,这可能会在某些场景下(比如Admin中使用commit=False)引发意外行为。
解决方案
这里提供两个可靠的解决思路,你可以根据需求选择:
方案一:使用UUID生成唯一种子(推荐)
直接用UUID作为生成hash的种子,完全避开pk和created_at的依赖,保证每个新增对象的种子绝对唯一:
import hashlib import uuid from django.db import models class Book(models.Model): hash = models.CharField(max_length=56, unique=True, null=True, editable=False) created_at = models.DateTimeField('Created at', auto_now_add=True) # .. 其他字段 def save(self, *args, **kwargs): if self.hash is None: # 生成唯一的UUID作为种子 seed = str(uuid.uuid4()).encode('utf-8') self.hash = hashlib.sha224(seed).hexdigest() # 一定要传递args和kwargs给父类方法 super().save(*args, **kwargs)
方案二:先保存生成pk,再生成hash
如果坚持要使用pk和created_at生成hash,可以先调用一次save生成pk,再计算hash后二次保存,注意要避免无限循环:
import hashlib from django.db import models class Book(models.Model): hash = models.CharField(max_length=56, unique=True, null=True, editable=False) created_at = models.DateTimeField('Created at', auto_now_add=True) # .. 其他字段 def save(self, *args, **kwargs): if self.hash is None: # 先保存一次,让数据库生成pk和created_at super().save(*args, **kwargs) # 现在pk已经有值了,生成hash string_seed = str(self.created_at).encode('utf-8') + str(self.pk).encode('utf-8') self.hash = hashlib.sha224(string_seed).hexdigest() # 再次保存,此时hash已存在,不会进入if分支 super().save(*args, **kwargs) else: super().save(*args, **kwargs)
额外提示
- 如果你想保留created_at作为种子的一部分,可以考虑提升它的时间精度:在Django 1.10+中,
DateTimeField支持auto_now_add=True并配合数据库的微秒级时间存储(比如MySQL的DATETIME(6)),但这只能降低重复概率,无法像UUID那样彻底避免。 - 永远记得在自定义
save方法时传递*args和**kwargs,否则会丢失Django内置的save参数支持。
内容的提问来源于stack exchange,提问作者GPraz
相关产品推荐
相关产品推荐

