有几种可能的情况很容易解决,而有害的则不是。
对于输入一个值的用户,然后在一段时间之后输入一个相同的值,然后在INSERT检测到问题之前进行一次简单的SELECT。这适用于以下情况:一个用户提交了一个值,一段时间后另一位用户提交了相同的值。
如果用户在一次调用代码时提交包含重复项的值列表(例如{ABC,DEF,ABC}),则应用程序可以检测和过滤重复项,可能会引发错误。您还需要在插入之前检查数据库是否不包含任何唯一值。
棘手的情况是,一个用户的写操作与另一个用户的写操作同时在DBMS内部,并且他们正在写相同的值。然后,您在他们之间进行了比赛。由于DBMS是(很可能-您不会说您正在使用的是哪种)DBMS,所以任何任务都可以在其执行的任何时刻暂停。这意味着user1的任务可以检查不存在的行,然后user2的任务可以检查不存在的行,然后user1的任务可以插入该行,然后user2的任务可以插入该行。在每个时候,任务都对自己在做正确的事情感到高兴。但是,全局会发生错误。
通常,DBMS可以通过锁定相关值来处理此问题。在此问题中,您将创建一个新行,因此尚无任何要锁定的内容。答案是范围锁定。正如它建议的那样,这将锁定一定范围的值,无论它们当前是否存在。一旦锁定,在释放锁定之前,其他任务将无法访问该范围。要获得范围锁,您必须指定SERIALIZABLE的隔离级别。任务检查后另一个任务连续潜行的现象被称为幻像记录。
在整个应用程序中将隔离级别设置为Serializable将会产生影响。吞吐量将降低。过去运行良好的其他比赛条件现在可能开始显示错误。我建议将其设置在执行引起重复代码的连接上,并保留应用程序的其余部分。
基于代码的替代方法是检查后写,而不是之前。插入也是如此,然后计算具有该哈希值的行数。如果重复,则回滚该操作。这可能会有一些不正常的结果。假设任务1先写任务2,然后任务1检查并找到重复的任务。即使是第一个,它也会回滚。同样,两个任务都可能检测到重复项,并且都可能检测到回滚。但是至少您会收到一条消息,即重试机制,并且没有新的重复项。回滚很不容易,就像使用异常来控制程序流一样。请注意,所有事务中的工作将被回滚,而不仅仅是引起重复的写入。而且,您必须进行显式事务处理,这可能会降低并发性。除非您在哈希表上有索引,否则重复检查的速度将非常慢。如果您愿意,也可以使其成为独一无二的产品!
正如您所评论的,真正的解决方案是唯一索引。在我看来,这应该适合您的维护时段(尽管您当然最了解您的系统)。说哈希是八个字节。对于一亿行,大约为1GB。经验表明,一小部分硬件可以在一两分钟之内完成这些行。重复检查和消除会增加这种情况,但是可以预先编写脚本。不过,这只是一个问题。