【问题标题】:Duplicate entry '...' for key 'PRIMARY' during the transaction交易期间密钥“PRIMARY”的重复条目“...”
【发布时间】:2015-09-02 22:30:18
【问题描述】:

这件事昨晚发生在我身上。我对错误的性质非常熟悉,但我仍然无法弄清楚是什么原因造成的。我可能有预感,但我不确定。我将从一些基本的应用信息开始:​​

我的应用有 3 个实体:LoanSystemPageTextPage。每当有人添加贷款时,就会将一个或多个系统页面添加到数据库中。基本上,它是这样的:

if ( $form->isValid()){
    $this->em->getConnection()->beginTransation();
    $this->em->persist($loan);
    $this->em->flush();

    while ($someCondition){
        $page = new SystemPage();
        //... Fill the necessary data into page
        $page->setObject($loan);
        $this->em->persist($page);
    }

    $this->em->flush();
    $this->em->getConnection()->commit();
}

请忽略可能的错别字,我是通过记住字面意思来写这篇文章的

实体Loan 映射到表loansSystemPage 映射(通过继承映射)到system_pagesbase_pages。后面两个都有id字段,设置为AUTO_INCREMENT

我的预感:还有一张名为text_pages 的表。鉴于一方面是text_pagesbase_pages,另一方面是system_pagesbase_pages 共享ID,我认为这很容易导致:

User1: Create BasePage, acquire autoincrement ID (value = 1)
User2: Create BasePage, acquire autoincrement ID (value = 1)
User1: Create TextPage, use the ID from step 1
User2: Create SystemPage, use the ID from step 2

这个理论有两个问题:

  • 交易。这就是我最初使用它们的原因
  • 在出错时,其他用户在应用上没有其他活动

重要提示:等待一分钟后,重新提交通过OK。

这可能是一些奇怪的MySQL 事务隔离错误吗?任何提示将不胜感激......

编辑:

部分数据库架构:

请忽略塞尔维亚语的列名

【问题讨论】:

  • 试图保持多个表的主键同步总是有点不确定。我无法准确解释发生了什么,除非您在创建基本页面和其他页面之间有某种重定向,但生命很短。为每个表提供自己的自增主键,并使用常规外键链接两者。
  • 关于这个的一些问题:哪个条目导致了这个错误,Systempage?如果出现错误,您是否有某种回滚?如果每个循环只填充一个实体,为什么要使用事务而不是刷新?最后,您能否确保这是当时唯一的请求,即使来自同一个用户(通过双击或类似的方式进行双重提交)?
  • Cerad,是的,它肯定是不确定的 :) 问题是,表 base_pagestext_pagessystem_pages 已经设置了它们的 AUTO_INCREMENT 字段。这实际上可能是问题的根本原因。我可以尝试调试问题并查看首先执行哪个查询(base_pagessystem_pages),然后在后面的查询中删除自动增量。但是完全随机发生(到目前为止只有一次)确实让人很难确信它修复了任何东西:)
  • CiTNOH,我相信rollback 是出现错误时的默认行为。否则,不会手动发出rollback 调用。事务的想法是确保无法创建LoanSystemPage 不是。一个实体没有另一个实体不应该是有效的。至于双重提交,我可以看到这可能是一个问题,但如果一个已经处于活动状态,执行是否应该在 beginTransaction() 上停止?

标签: php mysql symfony doctrine-orm transactions


【解决方案1】:

flush() 操作在一个事务中刷新所有更改,所以这里有冗余代码...

您没有说明是否可以重现此错误,如果您可以提供 db 架构会很方便。

【讨论】:

  • 我认为我无法重现该错误。这实际上是一个非常孤立的错误。我的理解是,没有flushcommit 什么都不做(在事务中时)。对吗?
  • 参见第二段:doctrine-orm.readthedocs.org/en/latest/reference/… - 如果您没有在代码开始/提交/回滚中明确说明,flush 会将您的更改保留在单个事务中,因此,您关于代码中事务的逻辑,据我所知,不需要,因为您不处理回滚或跨保存点。
  • 如果您无法重现该错误 - 该错误不存在。您必须能够重现它才能对其进行调试,否则我们处于非常灰色的区域......
  • 如果 base_page 在 Doctrine 继承映射中是 text_pagessystem_pages 的父级,那么 text_pages 和 system_pages 不应该有自动递增 ID - 这是怎么回事?
  • 如果您怀疑 MySQL 中的一些错误,即由于并发插入导致的自动递增值冲突 - 不要:dev.mysql.com/doc/refman/5.1/en/…
【解决方案2】:

这个问题似乎没有正确答案,只能猜测,所以我将根据自己的经验提供一些故障排除思路:

  1. 您提到应用程序上没有其他活动,但我会通过查看查询日志进行三次检查。必须执行重复的查询。

  2. 可能表单被意外提交了两次。用户双击提交按钮,或者如果 UI 没有响应,他们再次单击。您可以通过查看 Apache 日志文件来检查您的表单上相同时间戳附近的 POST 请求。您可能需要实现一些 javascript 代码来防止双击表单页面提交按钮。

  3. 您的预感可能非常接近正确,因为存在某种竞争条件。使用事务不会阻止竞争条件,但它们确实提供了优雅回滚的方法。将您的代码包装在 try/catch 块中,以便您可以捕获 Mysql 异常并向用户显示友好的错误和重试选项。

【讨论】:

  • 谢谢克里斯。我至少可以使用try-catch,以防止出现严重错误。至于额外的活动,我查看了服务器日志,没有任何证据。
猜你喜欢
  • 2017-02-17
  • 2012-05-01
  • 1970-01-01
  • 2012-07-23
  • 2013-09-30
  • 2015-09-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多