如何用该表唯一的不同整数替换 Django 的主键

Posted

技术标签:

【中文标题】如何用该表唯一的不同整数替换 Django 的主键【英文标题】:How to replace Django's primary key with a different integer that is unique for that table 【发布时间】:2016-09-30 05:46:35 【问题描述】:

我有一个 Django Web 应用程序,它使用默认的自动递增正整数作为主键。此密钥在整个应用程序中使用,并且经常插入到 URL 中。我不想将这个数字公开给公众,以便他们猜测我的数据库中的用户或其他实体的数量。

这是一个常见的要求,我已经看到了与我的类似问题的答案。大多数解决方案建议散列原始主键值。但是,这些答案都不完全符合我的需要。这些是我的要求:

    我想将主键字段类型保留为整数。 我还希望不必在每次读取或写入此值或与数据库进行比较时都对该值进行散列/取消散列处理。这似乎很浪费 最好只做一次:当记录最初插入数据库时​​ 散列/加密函数不需要是可逆的,因为我不需要恢复原始顺序密钥。散列值只需唯一。 哈希值只需要对该表是唯一的,而不是普遍唯一的。 散列值应尽可能短。我希望避免使用过长(20 多个字符)的网址

实现这一目标的最佳方法是什么?下面的工作吗?

def hash_function(int):
    return fancy-hash-function # What function should I use??


def obfuscate_pk(sender, instance, created, **kwargs):
    if created:
        logger.info("MyClass #%s, created with created=%s: %s" % (instance.pk, created, instance))
        instance.pk = hash_function(instance.pk)
        instance.save()
        logger.info("\tNew Pk=%s" % instance.pk)

class MyClass(models.Model):
    blahblah = models.CharField(max_length=50, null=False, blank=False,)


post_save.connect(obfuscate_pk, sender=MyClass)

【问题讨论】:

如果长度没有问题,使用 UUID。 UUID 不是整数。 如果需要整数,可以表示为整数。但在你的情况下,我会保持正常递增的 PK 并只使用向用户公开的对象存储一个额外的查找键。 如何构造查找键,使其成为唯一的、不可猜测的数字?? Python 的uuid lib 有几个选项可以生成一个。您可能想使用随机的。 str(uuid.uuid4()) 【参考方案1】:

理念

我会向您推荐 Instagram 使用的相同方法。他们的要求似乎紧跟您的要求。

生成的 ID 应该可以按时间排序(因此是一张照片 ID 列表,用于 例如,可以在不获取更多信息的情况下进行排序 照片)ID 理想情况下应该是 64 位(对于更小的索引,更好 Redis 等系统中的存储)系统应该引入尽可能少的新 尽可能“移动部件”——我们能够做到的很大一部分 用很少的工程师来扩展 Instagram 是通过选择简单的, 我们信任的易于理解的解决方案。

他们提出了一个基于时间戳的 41 位、13 位数据库分片和 10 位用于自动增量部分的系统。因为你似乎没有使用碎片。您可以只为基于时间的 copmonent 设置 41 位,并随机选择 23 位。如果您同时插入记录,这确实会产生极不可能的 830 万分之一的冲突机会。但在实践中,你永远不可能做到这一点。那么一些代码怎么样:

生成 ID

START_TIME = a constant that represents a unix timestamp

def make_id():
    '''
    inspired by http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
        '''
    
    t = int(time.time()*1000) - START_TIME
    u = random.SystemRandom().getrandbits(23)
    id = (t << 23 ) | u
    
    return id


def reverse_id(id):
    t  = id >> 23
    return t + START_TIME 

注意,上面代码中的START_TIME 是一些任意的开始时间。您可以使用 time.time()*1000 ,获取值并将其设置为 START_TIME

请注意,我发布的reverse_id 方法可以让您找出记录的创建时间。如果您需要跟踪该信息,您可以这样做,而无需为其添加另一个字段!所以你的主键实际上是在节省你的存储空间而不是增加它!

模型

这就是你的模型的样子。

class MyClass(models.Model):
   id = models.BigIntegerField(default = fields.make_id, primary_key=True)  

如果您在 django 之外对数据库进行更改,则需要创建与 make_id 等效的 sql 函数

作为脚注。这有点像 Mongodb 用来为每个对象生成 _ID 的方法。

【讨论】:

尽管它在数学上并不完美,(碰撞的可能性很小)如果它对 Instagram 来说足够好,它对我来说已经足够好了!非常感谢。在我实施此解决方案后(可能在即将到来的周末),我将奖励你赏金 哎呀,应该提到 START_TIME 应该是一个 int。例如 1464972048475 好的。谢谢make_id() 现在正在工作。但是当我尝试创建一个对象时它失败了:gist.github.com/saqib-zmi/4c3fad6d922ea0437c297065f71ba2d3 让我们continue this discussion in chat。 id = models.BigIntegerField(default=make_id(), primary_key=True) 仅供参考,我的数据库是 MariaDB,如果这有什么不同的话。 (但是,Django 不知道。它是 mysql 的直接替代品)【参考方案2】:

你需要分离两个关注点:

    主键,目前是一个自动递增的整数,是可以在数据库级别强制执行的简单、相对可预测的唯一标识符的最佳选择。

    这并不意味着您必须在您的 URL 中将其公开给用户。

我建议向您的模型添加一个新的 UUID 字段,并重新映射您的视图以使用它而不是 PK 来进行对象查找。

【讨论】:

【参考方案3】:

一个非常简单的解决方案是在将 ID 发送到外部源之前对其进行加密。你可以在回来的路上解密它。

【讨论】:

如果您返回的行数超过几行,那会有点贵,不是吗?【参考方案4】:

保留AUTO_INCREMENT,但以半秘密的方式传递它:在 cookie 中。建立 cookie、设置和读取它需要一些编码。但是除了严重的黑客之外,所有的 cookie 都是隐藏的。

【讨论】:

没有。任何人只要点击浏览器上的几个按钮,就可以检查 Cookie。依靠用户的技术不熟练并不是实现安全的好方法。 @SaqibAli - 没错,您可以看到 您的 cookie。但是,如果不先入侵您的计算机,另一台计算机上的人应该无法看到它们。而且,cookie(在您的浏览器中)对发布它们的同一域可见。也就是说,其他网站无法通过您的浏览器窥探您的 cookie。 (是的,有 XSS,但大多数网站都可以防止这种情况发生。)

以上是关于如何用该表唯一的不同整数替换 Django 的主键的主要内容,如果未能解决你的问题,请参考以下文章

约束条件

如何用redis来生成唯一Id

MySQL - 如何创建一个新表,该表是两个现有表的主键的连接

MySql

约束条件

具有多个附加到列表的主键的数据库