当您无法添加唯一索引时,有哪些可能的方法来避免重复


10

我陷入了并发问题。

用户发送2 o 3事务来保留某些不应在数据库中重复的数据是一个典型的问题,如果有重复的记录,您应该返回一个错误。

当您仅可以向存储哈希的列添加索引(唯一)时,此问题就很容易。

但是在这种情况下,我有一个巨大的表(可能有数百万条记录),而我不能只修改该表。

实际上,我们有一列存储数据的散列,该散列不应重复但未设置唯一索引。

我正在尝试在我的Java代码上检查是否在刷新之前存在,仍然会重复。

我对此可能的解决方案是:

  • 创建一个触发器,以检查我要插入的哈希表上是否已经存在。
  • 创建另一个表来存储该表的唯一索引,并将外键添加到主表。
  • 坐在胎位上哭

您的哈希检查是否由于哈希冲突或检查错误而失败?
candied_orange

4
我没收到你的问题。因此,您不希望为拥有数百万条记录的所有巨大表建立索引,而是宁愿为要添加的下百万条记录中的每条读取,现有的数百万条查找双精度吗?还是重复一些信息并添加联接以进行检查?
克里斯托弗(Christophe)

问题是,为了进行此更改,我被警告说我们需要大量空间和较长的服务停机时间,才能满足某些要求,我们的服务每月不能超过2小时。我知道最好的方法是在此表上进行维护,但这是我目前无法做的事情,因此我们需要一种解决方法。
rafuru

4
我不明白-为什么与仅向现有表中添加索引相比,添加触发器或添加其他表以“模拟”索引所花费的停机时间更少?
布朗

2
@rafuru:谁说您需要创建一个唯一索引?使用标准的非唯一索引可能需要快速查找具有相同哈希值的所有行。
Doc Brown

Answers:


3

有几种可能的情况很容易解决,而有害的则不是。

对于输入一个值的用户,然后在一段时间之后输入一个相同的值,然后在INSERT检测到问题之前进行一次简单的SELECT。这适用于以下情况:一个用户提交了一个值,一段时间后另一位用户提交了相同的值。

如果用户在一次调用代码时提交包含重复项的值列表(例如{ABC,DEF,ABC}),则应用程序可以检测和过滤重复项,可能会引发错误。您还需要在插入之前检查数据库是否不包含任何唯一值。

棘手的情况是,一个用户的写操作与另一个用户的写操作同时在DBMS内部,并且他们正在写相同的值。然后,您在他们之间进行了比赛。由于DBMS是(很可能-您不会说您正在使用的是哪种)DBMS,所以任何任务都可以在其执行的任何时刻暂停。这意味着user1的任务可以检查不存在的行,然后user2的任务可以检查不存在的行,然后user1的任务可以插入该行,然后user2的任务可以插入该行。在每个时候,任务都对自己在做正确的事情感到高兴。但是,全局会发生错误。

通常,DBMS可以通过锁定相关值来处理此问题。在此问题中,您将创建一个新行,因此尚无任何要锁定的内容。答案是范围锁定。正如它建议的那样,这将锁定一定范围的值,无论它们当前是否存在。一旦锁定,在释放锁定之前,其他任务将无法访问该范围。要获得范围锁,您必须指定SERIALIZABLE的隔离级别。任务检查后另一个任务连续潜行的现象被称为幻像记录

在整个应用程序中将隔离级别设置为Serializable将会产生影响。吞吐量降低。过去运行良好的其他比赛条件现在可能开始显示错误。我建议将其设置在执行引起重复代码的连接上,并保留应用程序的其余部分。

基于代码的替代方法是检查写,而不是之前。插入也是如此,然后计算具有该哈希值的行数。如果重复,则回滚该操作。这可能会有一些不正常的结果。假设任务1先写任务2,然后任务1检查并找到重复的任务。即使是第一个,它也会回滚。同样,两个任务都可能检测到重复项,并且都可能检测到回滚。但是至少您会收到一条消息,即重试机制,并且没有新的重复项。回滚很不容易,就像使用异常来控制程序流一样。请注意,所有事务中的工作将被回滚,而不仅仅是引起重复的写入。而且,您必须进行显式事务处理,这可能会降低并发性。除非您在哈希表上有索引,否则重复检查的速度将非常慢。如果您愿意,也可以使其成为独一无二的产品!

正如您所评论的,真正的解决方案是唯一索引。在我看来,这应该适合您的维护时段(尽管您当然最了解您的系统)。说哈希是八个字节。对于一亿行,大约为1GB。经验表明,一小部分硬件可以在一两分钟之内完成这些行。重复检查和消除会增加这种情况,但是可以预先编写脚本。不过,这只是一个问题。


2

实际上,我们有一列存储数据的散列,该散列不应重复但未设置唯一索引。

检查哈希冲突是一个很好的第一步,但是请注意,如果重新启动则不能保证同一程序将对相同数据产生相同的哈希。许多“快速”哈希函数使用内置的prng,该prng在程序启动时进行播种。如果无论如何都需要使哈希始终保持相同,请使用加密哈希,就像在本应用程序中一样。请注意,您不需要良好或安全的加密哈希。

第二步是实际检查数据的相等性,因为最好的哈希函数有时会导致冲突,因为您(通常)是在减少数据的熵。

所以:

步骤1:检查您是否在加密哈希上遇到冲突

步骤2:如果哈希匹配,请检查实际数据是否相同


我看不到这如何回答问题。让我们暂时假设可用的哈希列由确定性哈希函数填充(否则,任何利用它的尝试都是没有意义的)。据我了解,问题在于数据库中该哈希列上没有索引,因此即使您回答的第一步(检查是否存在冲突)也仍然需要对表中的每个新记录进行全表扫描,几百万条记录,这可能会变得太慢。
布朗

这是您不创建索引所能做到的最好的选择。哈希扫描至少意味着您只需要检查一列,这比检查它们本来必须检查的许多列要快得多。
Turksarama

我很确定,即使无法创建索引(在这种情况下也可能是这样),OP的原始建议“ 创建另一个表来存储该表的唯一索引并将外键添加到主表 ”也能起到很大的作用更有意义。
布朗

确定性哈希和密码哈希是两个正交的概念,不是吗?加密散列可能不是确定性的,反之亦然,确定性散列很可能没有加密强度。
Newtopian

它们不是同一件事,但也不是正交的。加密散列是确定性散列的子集,但是没有人真正为制作非加密确定性散列而烦恼,除非您出于某些原因特别希望它是可逆的。
Turksarama

2

用唯一的主键创建一个新表

在客户端,开始为每个记录生成GUID,以便您可以检测简单的重新发送。

将新记录放入新表中,这样至少对于新数据的接收是有利的。

在新表“ CheckedAgainstOldData”中有一列

有一个后端任务,您可以执行当前当前的慢速哈希检查,以查看它是否可以在旧数据中找到重复项并相应地设置标志,此时拒绝重复项,然后将通知发送回客户端。

同时,还有另一个后端任务,该任务将数据从旧表移到新表,使用哈希检查检查重复项并生成GUID。

您可以将此任务运行几天(如果需要),从而在不停机的情况下传输数据。

传输完成后,您可以关闭慢速的“ CheckedAgainstOldData”过程。并将所有数据传输到单个表中。

坦率地说,如果问题像您描述的那样严重并且软件很旧,那么您将有成千上万的重复项。


1

假设数据来自“用户”是指坐在键盘上的某人,并且欺骗是由于两个用户同时输入相同数据而产生的。尝试添加一个在触发开始时会导致随机延迟的函数。给它最少的时间,即将新记录写入表所花费的时间,最长的时间可能不超过纳米世纪左右。这样,当您收到欺骗请求时,应该先完成第一个请求,并且存在触发器应返回正确的结果。(澄清:每个呼叫应具有自己独特的随机延迟时间,以及与ALOHA协议相同的原理)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.