找到无碰撞随机数的有效方法

时间:2011-07-24 17:45:06

标签: php mysql random primary-key

我有一个用户表,用户ID是公开的。但我想混淆的数量 注册用户和项目的趋势,所以我不想有公共增量ID。

创建新用户时,我希望找到一个大于某个数字但尚未存在于数据库中的随机整数。

天真的代码:

<?php
    $found = false;
    while(!$found) {
      $uid = rand(1000000000,4294967295) // find random number betwen minimum and maximum
      $dbh->beginTransaction();
      // check if user id is in use, and if not insert it
      if($dbh->query("SELECT * FROM users WHERE uid = $uid")) {
        $dbh->exec("INSERT INTO users (uid) VALUES ($uid)");
        $found = true;
      }
      $dbh->commit();
    }
    // we just got our new uid ...
?>

这会起作用,但可能效率低下。确实有一个很大的范围,击中未使用的uid的可能性很高。但是,如果我想使用较小的范围,因为我不想拥有这么长的用户名呢?

我担心的例子:

  • 所有用户ID的60%正在使用中
  • 击中未使用的uid的几率为0.4
  • 第一次尝试的成功率为0.4%
  • 如果第一次没有成功,第二次尝试的概率为0.6 * 0.4
  • 所以最多有两次尝试我有0.4 + 0.6 * 0.4的能力(是对吗??)

因此,我想到的一种优化方法如下:

  • 找到一个随机数,检查它是否有空,如果不是,则将其递增1并再试一次等​​等
  • 如果达到最大数量,则继续使用最小数字

那应该给我一个最大运行时间为O(范围)的数字

这听起来很糟糕,但我认为不是,因为我向数据库提交了随机数,并且他们都在初学者身上是不太可能的。那真的有多好/坏?

我认为这样可行但我希望它更好

那么这个呢?

  • 找一个随机数
  • 在数据库中查询整个范围内占用的数量,从该数字开始(这第一步很简单......)
  • 如果该范围内有数字,请将范围除以一半再试一次。从初始号码开始
  • 如果有数字占用将范围除以一半再试一次。从初始号码开始

如果我正确思考,这将给出一个最多为O(log(范围))时间的数字。

这非常令人满意,因为log()非常好。但是我认为这种方法通常会尽可能地糟糕。因为使用我们的随机数字,我们可能总是在较大的间隔中命中数字。

所以在开始时我们的纯随机方法可能更好。

那么有这样的限制呢

  • 选择当前使用的号码数
  • 大于X,对数范围方法
  • 如果不是,请使用纯随机方法

X是什么?为什么?

所以最后一个问题:

这很简单,同时也非常复杂。

我认为这是一个标准问题,因为很多系统都使用随机ID(支持票证等),所以我无法想象我是第一个偶然发现它的人。

你会如何解决这个问题?任何输入都是适当的!

我可以使用maby现有的类/程序吗?

或者maby我可以使用哪些数据库函数?

我想在PHP / Mysql

中完成

重要编辑:

我只考虑了范围/对数解决方案。这似乎是完全废话对不起我的措辞因为:

  • 如果我在开始时点击占用的号码怎么办?

然后,如果它只有1,那么我将我的范围划分得很长。即使这样,这个数字也会出现。

所以从开始就完全和纯随机方法一样,只会更糟......

我有点尴尬我做了这个,但我会留下来因为我认为这是一个过于复杂的思想的好例子!

8 个答案:

答案 0 :(得分:12)

如果p是正在使用的ID的比例,那么您的“天真”解决方案平均需要1 /(1-p)次尝试才能找到未使用的ID。 (见Exponential distribution)。在占用率为60%的情况下,这仅仅是1 / 0.4 = 2.5查询...

您的“改进”解决方案需要有关log(n)数据库调用,其中n是正在使用的ID数。这比“天真”的解决方案要多得多。此外,您改进的解决方案是不完整的(例如,它不处理子范围中的所有数字都采用的情况,并且没有详细说明您递归到的子范围)并且实现引导更复杂。

最后,请注意,如果数据库提供非常严格的事务隔离(您的扩展性很差),并且可能不是数据库系统的默认行为,那么您的实现将只是线程安全的。如果结果是一个问题,你可以推测性地插入一个随机id,并在违反约束的情况下重试。

答案 1 :(得分:2)

如果您不想测试已使用的数字,可以根据数据库$id_k中自动递增的ID创建一个计算随机ID $id的函数:

$id_k = transpose($id);

该功能具有反向表兄或能够透明地转换(理想情况下):

$id = transpose($id_k);

然后,您可以在网站上使用转置的ID。

我想到的另一个想法是你预先计算每个递增id的随机id,这样你就可以更好地控制数据库的使用。

答案 2 :(得分:2)

Joe,只需按上述方法实施您的算法,不用担心。看看:如果击中使用的id的概率是p = 0.6,那么你连续N次使用id的概率是p ^ N.这呈指数下降!我建议将ID密度设置得更低,例如至p = 0.1。 那么你连续10次尝试没有成功的概率是p ^ 10 = 0.1 ^ 10 = 1e-10 !!!绝对可以忽略不计。

不要担心碰撞并寻求解决方案。

答案 3 :(得分:2)

你如何从不会发生冲突的东西和一些小范围内的随机数组成数字。

 ddddd-(rrr+n)

ddddd例如是你的系统生存的天数,rrr是每天选择的随机数,n是一天内的增量。

鉴于任何一个号码,当天不知道rrr的人无法推断出在某一天创建了多少用户。

答案 4 :(得分:1)

当你启动应用程序时,选择100个数字的随机范围(例如100 - 199,1000 - 1099,5400 - 5499),检查第一个,如果它不在我们知道的数据库中(基于此所有100个都是免费的算法)。将此范围的开头存储在内存中。

然后只需分配这些直到你用完(或你的应用程序回收),然后选择另一个随机范围。所以你只需要每100个用户一次去数据库。

这类似于Nhibernate hi / lo方法(除了随机位)。

显然会调整为100,具体取决于您分配ID的速率与内存中应用程序的典型生命周期相比。

答案 5 :(得分:1)

您可以简单地使用任何哈希混洗算法来通过已知数量的用户生成新的Id值(保持此值是通常的做法)。这种方法可能比您当前的解决方案更好,因为相应的算法可能会产生更少的冲突。关键是选择具有适当强度和分布均匀性的算法。

答案 6 :(得分:1)

要扩展meriton'sTomas Telensky's个答案,如果您希望保持用户ID简短,同时确保您没有用完,则可以随机选择每个ID,比如说,范围1到10 * n +1000,其中 n 是您拥有的当前用户数。

这样,您的有效用户ID空间永远不会超过10%,而用户ID(从长远来看)将比您按顺序分配它们的时间长一个数字。当然,缺点是ID将不再与注册顺序完全不相关:如果有人拥有ID 5851,您知道他们必须至少是第486位注册用户并且他们不太可能,例如, 50000。 (当然,如果您手动调整范围以容纳更多用户,则会引入相同类型的相关性。)

当然,您可以调整上面的常量10和1000:它们越大,用户ID就越长,越随机。

答案 7 :(得分:1)

您可以使用symmetric-key加密(例如AES)来加密计数器。如果你使用整个输出(AES为128位),那么你可以保证没有冲突,这是一个可逆的映射。

128位可能比您想要处理的更多 - 它是一个32位十六进制数,39位十进制数。您可以使用64位加密算法,例如DESBlowfishMisty(16位十六进制数,20位十进制数)。