违反主键约束,多个用户

Posted

技术标签:

【中文标题】违反主键约束,多个用户【英文标题】:Violation of primary key constraint, multiple users 【发布时间】:2010-06-11 21:41:35 【问题描述】:

假设 UserA 和 UserB 都打开了一个应用程序并且正在处理相同类型的数据。 UserA 向表中插入一条值为 10 (PrimaryKey='A') 的记录,UserB 当前看不到 UserA 输入的值,并尝试插入新值 20 (PrimaryKey='A')。在这种情况下,我想要的是 DBConcurrencyException,但我所拥有的是主键违规。我明白为什么,但我不知道如何解决这个问题。处理这种情况的好习惯是什么?我不想在更新数据库之前合并,因为我想要一个错误通知用户多个用户更新了这个数据。

【问题讨论】:

主键在哪里生成? DB、SP .NET 代码? 【参考方案1】:

在这种情况下我想要的是 DBConcurrencyException,但我所拥有的是主键违规。我明白为什么

这是这种情况的正确例外。您说您想通知用户该值已被插入,因此只需捕获主键异常,然后将用户友好的消息吐回即可。

【讨论】:

【参考方案2】:

这是您必须做出的设计决定 - 您要使用悲观锁定还是乐观锁定?

我太懒了-quoted from this thread:

这些是用于处理多用户问题的方法。如何处理两个人要同时更新同一条记录这一事实?

    什么都不做

    用户 1 读取记录 用户 2 读取相同的记录 用户 1 更新该记录 用户 2 更新同一条记录

    用户 2 现在已经覆盖了用户 1 所做的更改。它们完全消失了,就好像它们从未发生过一样。这称为“丢失更新”。

    读取时锁定记录。 悲观锁定

    用户 1 读取记录并通过在记录上放置排他锁来锁定它(FOR UPDATE 子句) 用户 2 尝试读取并锁定同一条记录,但现在必须在用户 1 后面等待 用户 1 更新记录(当然还有提交) 用户 2 现在可以读取记录用户 1 所做的更改 用户 2 使用来自用户 1 的更改更新记录

    丢失更新问题已解决。这种方法的问题是并发性。用户 1 正在锁定一条他们可能永远不会更新的记录。用户 2 甚至无法读取记录,因为他们在读取时也需要排他锁。这种方法需要太多的独占锁定,并且锁定的寿命太长(通常跨越用户控制 - 绝对 no-no)。这种方法几乎从未实施过。

    使用乐观锁定。 乐观锁在读取时不使用排他锁。相反,在更新期间会进行检查,以确保记录自读取后未被更改。这可以通过检查表中的每个字段来完成。 IE。 UPDATE Table1 SET Col2 = x WHERE COL1=:OldCol1 AND COl2=:OldCol AND Col3=:OldCol3 AND... 当然,这有几个缺点。首先,您必须已经从表中选择了每一列。其次,你必须构建并执行这个庞大的语句。 大多数 人通过单个列来实现这一点,通常称为时间戳。此列仅用于实现乐观并发。它可以是数字或日期。这个想法是在插入行时给它一个值。每当读取记录时,也会读取时间戳列。执行更新时,会检查时间戳列。如果它在 UPDATE 时的值与读取时的值相同,则一切正常,执行 UPDATE 并且 时间戳已更改!。如果 UPDATE 时的时间戳值不同,则会向用户返回错误 - 他们必须重新读取记录,重新进行更改,然后再次尝试更新记录。

    用户1读取记录,包含21的时间戳 用户2读取记录,包括时间戳21 用户 1 尝试更新记录。 had(21)中的timestamp与database(21)中的timestamp匹配,所以执行更新,timestamp为update(22)。 用户 2 尝试更新记录。手中的时间戳(21) 与数据库中的时间戳不匹配(22),因此返回错误。用户 2 现在必须重新读取记录,包括新的时间戳 (22) 和用户 1 的更改,重新应用他们的更改并重新尝试更新。

【讨论】:

除了这两种方法适用于对现有行的更新。问题是关于具有相同主键的插入,在这种情况下,是数据库引发了异常,而乐观/悲观锁并不真正适用。 @p.marino:悲观/乐观锁定是为了处理并发,而不是更新。 您可能已经注意到,您(懒惰地)复制的示例详细介绍了更新。事实上,乐观锁通常是通过添加一个带有数值的额外字段来实现的,该数值将由第一个访问记录以更新它的人递增。例如,对于具有基于 DB 的存储的 Web 应用程序来说,这是一种常见的策略。它与所述问题无关。【参考方案3】:

如果您在并发用户插入 NEW 记录时遇到 PK 违规,则会发生以下两种情况之一:

违规发生在自然键上,即具有业务价值的键,如用户名或类似名称。 PK违规是由于业务流程缺陷而发生的,即。两个不同的操作员尝试插入相同的业务项目。如何反应的逻辑完全由业务领域特定的规则驱动,我们不可能给出任何建议。

违规发生在代理键上,即。像 CustomerID 或类似的标识符。在这种情况下,该缺陷完全依赖于应用程序代码,因为这意味着它使用有缺陷的算法来生成新的 ID。同样,如果不了解新 ID 的生成方式,就无法给出有效的建议。

【讨论】:

【参考方案4】:

一种解决方案可能涉及到桌子上的INSTEAD OF INSERT 触发器。

在这里,您将覆盖触发器中的 INSERT 语句。当您检测到主键“A”的行或值已存在时,您将有机会RAISEERROR。

【讨论】:

为什么要增加复杂性并使事情变慢?正如下一个答案正确暗示的那样,该异常保证存在并且是正确的-最坏的情况是他可以让应用程序代码捕获它并重新抛出他最喜欢的那种(即使我真的不明白他为什么想做所以)。 @p.marino:确实——这只是一种方法。 YMMV。

以上是关于违反主键约束,多个用户的主要内容,如果未能解决你的问题,请参考以下文章

违反主键约束查询

指示违反主键约束的异常的名称是啥?

主键约束是啥和啥组合?

Hibernate主键生成策略strategy = "increment"报错违反唯一性约束

Hibernate 和 HSQLDB 违反主键完整性约束

更新时SQL违反主键约束