繁体   English   中英

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

[英]How to replace Django's primary key with a different integer that is unique for that table

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

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

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

实现这一目标的最佳方法是什么? 以下会起作用吗?

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)

想法

我会向您推荐Instagram使用的相同方法。 他们的要求似乎与您的要求密切相关。

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

他们提出了一个基于时间戳的 41 位系统,13 位数据库分片和 10 位自动增量部分。 因为您似乎没有使用碎片。 您可以只使用 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的等效项make_id为 sql 函数

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

您需要分离两个关注点:

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

  2. 这并不意味着您必须在您的 URL 中向用户公开它。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM