从容量为一百万行的表中查找未使用的ID的最佳算法是什么?

时间:2020-03-06 14:22:26  来源:igfitidea点击:

详细说明 ..
a)一个表(BIGTABLE)可以容纳一百万行,并具有一个主键作为ID。 (随机且唯一)
b)可以使用什么算法来得出到目前为止尚未使用的ID。此数字将用于在表BIGTABLE中插入另一行。

更新了问题的更多详细信息。
C)该表已经有大约10万行,并且主键未设置为标识。
d)当前,将生成一个随机数作为主键,并在该表中插入一行,如果插入失败,则会生成另一个随机数。问题是有时会陷入一个循环,并且生成的随机数是非常随机的,但是不幸的是,它们已经存在于表中。因此,如果我们在一段时间后重试随机数生成数字,它将起作用。
e)sybase rand()函数用于生成随机数。

希望这个问题的补充有助于阐明一些观点。

解决方案

如果ID纯粹是随机的,则没有算法可以类似的随机方式找到未使用的ID,而不会强行强制执行。但是,只要随机唯一ID的位深度足够大(例如64位),我们就可以避免只有一百万行的冲突。如果插入时发生碰撞,请重试。

选择一个随机数,检查它是否已经存在,然后继续尝试直到找到不存在的那个。

编辑:或者
更好的是,跳过检查,仅尝试插入具有不同ID的行,直到它起作用为止。

根据数据库,我们可以选择使用序列器(oracle)或者自动增量(mysql,ms sql等)。或者最后一招做一个选择max(id)+ 1作为新的id只是要小心并发请求,因此我们不会以相同的max-id结束两次使用即将到来的insert语句将其包装在锁中

设置关键字段的唯一性和标识性,我们不必担心。

为什么唯一ID是随机的?为什么不使用IDENTITY?
如何为现有行选择ID。

最简单的操作可能是(从BIGTABLE中选择Max(ID)),然后确保新的" Random" ID大于该ID。

编辑:根据添加的信息,我建议我们被搞砸了。

如果可以的话:复制表,然后重新定义表并使用"标识列"。

如另一个答案所述,如果我们确实需要一个真正随机的标识符:将PK设为两个字段。标识字段,然后是随机数。

如果我们根本无法更改表结构,则在尝试插入之前检查ID是否存在可能是我们唯一的选择。

确实没有一个好的算法。我们可以使用以下基本构造来查找未使用的id:

int id;
do {
  id = generateRandomId();
} while (doesIdAlreadyExist(id));
doSomethingWithNewId(id);

如果我们经常需要执行此操作,则可能需要维护实时(非数据库)数据结构以快速回答此问题。十棵树会很好。当应用程序启动时,它将通过读取数据库中的键来填充树,然后使其与数据库中进行的各种插入和删除操作保持同步。只要应用程序是唯一一个更新数据库的应用程序,就可以在验证下一个大型随机密钥尚未使用时非常快地查询树。

我已经看过很多次通过蛮力使用随机数生成器来做到这一点,这总是一个坏主意。在数据库外部生成一个随机数并尝试查看它是否存在将对应用程序和数据库造成很大的压力。这可能导致2个进程选择相同的ID。

最好的选择是使用MySQL的自动增量功能。其他数据库具有类似的功能。我们将获得唯一的ID,并且不会出现并发问题。

每次寻找唯一值都扫描该表中的每个值可能是一个坏主意。我认为这样做的方法是在另一个表中有一个值,对该表进行锁定,读取该值,计算下一个ID的值,写入下一个ID的值,然后释放锁。然后,我们可以放心使用读取的ID,当前进程是唯一拥有该唯一值的进程。不确定它的扩展程度。

另外,也可以使用一个GUID作为ID,因为每个新生成的GUID应该都是唯一的。

第一个问题:这是计划中的数据库还是已经运行的数据库。如果内部已经有数据,那么bmdhacks的答案是正确的。如果它是计划中的数据库,则这是第二个问题:
主键真的需要随机吗?如果答案是肯定的,则使用一个函数从已知种子中创建一个随机ID,并使用一个计数器来知道已创建了多少个ID。创建的每个ID都会使计数器递增。
如果我们将种子保密(即,将种子命名为私有并声明为私有),则其他任何人都无法预测下一个ID。

新ID是否也必须是随机的?如果是这样,最好的答案就是循环(随机化,测试是否存在),直到找到一个不存在的为止。

如果数据恰好是随机的,但不是很严格的约束,则可以只使用SELECT MAX(idcolumn),以适合数据的方式递增,然后将其用作下一条记录的主键。

我们需要自动执行此操作,因此或者锁定表,或者使用其他适合数据库配置和架构的并发控件。存储的proc,表锁,行锁,SELECT ... FOR UPDATE等。

请注意,无论采用哪种方法,我们都可能需要处理失败的事务。从理论上讲,我们可能会在第一个问题中遇到重复的密钥问题(尽管如果密钥空间稀疏,这不太可能发生),并且我们可能会使用SELECT ... FOR UPDATE之类的方法在某些数据库上陷入僵局。因此,请务必检查并在出错时重新启动事务。

最好的选择是使密钥空间足够大,以使发生碰撞的可能性极低,然后不必担心。如前所述,GUID将为我们完成此任务。或者,我们可以使用纯随机数,只要它具有足够的位数即可。

该页面具有用于计算碰撞概率的公式。

首先检查是否未使用Max(ID)+1.

如果Max(ID)+ 1超过最大值,则在顶部选择一个有序的块,然后开始向后循环查找孔。重复这些块,直到数字用完(在这种情况下会引发大错误)。

如果找到了"孔",则将ID保存在另一个表中,我们可以将其用作下一种情况的起点来保存循环。

跳过任务本身的推理,这是唯一的算法

  • 会给你一个不在桌子上的ID
  • 将用于在表格中插入新行
  • 将导致表格仍具有随机唯一ID

正在生成一个随机数,然后检查它是否已被使用

问题当然是:为什么要一个随机ID?

我遇到类似要求的一种情况是使用Web应用程序的客户端ID:客户端使用自己的客户端ID(存储在Cookie中)标识自己,因此必须很难用蛮力猜测另一个客户端的ID(因为这样做会允许劫持他的数据)。

我采用的解决方案是将顺序int32与随机int32合并以获得一个int64用作客户端ID。在PostgreSQL中:

CREATE FUNCTION lift(integer, integer) returns bigint AS $$
SELECT (::bigint << 31) + 
$$ LANGUAGE SQL;

CREATE FUNCTION random_pos_int() RETURNS integer AS $$
select floor((lift(1,0) - 1)*random())::integer
$$ LANGUAGE sql;

ALTER TABLE client ALTER COLUMN id SET DEFAULT
lift((nextval('client_id_seq'::regclass))::integer, random_pos_int());

生成的ID是"半"随机的,而另一个"半"保证我们不能两次获得相同的ID:

select lift(1, random_pos_int());  => 3108167398
select lift(2, random_pos_int());  => 4673906795
select lift(3, random_pos_int());  => 7414644984
...

在这种情况下,最好的算法是生成一个随机数,然后进行选择以查看其是否存在,或者如果数据库错误地尝试添加它,则尝试添加它。根据密钥范围以及有多少条记录,这可能会花费很少的时间。它也具有峰值能力,并且根本不一致。

是否可以在BigTable上运行一些查询,看看是否有可以利用的范围? IE。在100,000和234,000之间还没有ID,因此我们可以在其中添加ID吗?

开箱即用。

为什么不提前生成随机数呢?这样,当我们在bigtable中插入新行时,已经进行了检查。这将使对bigtable的插入成为恒定时间的操作。

我们最终将必须执行检查,但是可以将其转移到第二个过程中,该过程不涉及插入bigtable的敏感过程。

或者生成数十亿个随机数,然后删除重复数,那么我们就不必担心很长时间了。

为什么不给随机数创建者添加当前日期(以秒为单位)。这样,拥有相同ID的唯一方法是在同一秒钟创建两个用户,并且生成器为其分配相同的随机数。