Php 学说上的竞合

Php 学说上的竞合,php,mysql,concurrency,transactions,doctrine-orm,Php,Mysql,Concurrency,Transactions,Doctrine Orm,我有一个应用程序,运行在php+mysql平台上,使用Doctrine2框架。我需要在一个http请求期间执行3个db查询:第一次插入,第二次选择,第三次更新。更新取决于SELECT查询的结果。并发http请求的概率很高。如果出现这种情况,并且数据库查询混淆(例如INS1、INS2、SEL1、SEL2、UPD1、UPD2),则会导致数据不一致。如何确保INS-SEL-UPD操作的原子性?我是否需要使用某种类型的锁,或者事务就足够了 保证在所有情况下都能使用表宽。但它们非常糟糕,因为它们会阻止并发

我有一个应用程序,运行在php+mysql平台上,使用Doctrine2框架。我需要在一个http请求期间执行3个db查询:第一次插入,第二次选择,第三次更新。更新取决于SELECT查询的结果。并发http请求的概率很高。如果出现这种情况,并且数据库查询混淆(例如INS1、INS2、SEL1、SEL2、UPD1、UPD2),则会导致数据不一致。如何确保INS-SEL-UPD操作的原子性?我是否需要使用某种类型的锁,或者事务就足够了

保证在所有情况下都能使用表宽。但它们非常糟糕,因为它们会阻止并发,而不是处理并发。 但是,如果脚本在很短的时间内持有锁,那么这可能是一个可接受的解决方案

如果您的表使用InnoDB引擎(不支持MyISAM的事务),事务是最有效的解决方案,但也是最复杂的解决方案

根据您的特定需要(在同一个表中,第一次插入、第二次选择、第三次更新取决于选择查询的结果):

  • 启动交易
  • 插入您的记录。在提交您自己的事务之前,其他事务将不会看到这些新行(除非您使用非标准)
  • 使用选择您的记录。现在这些行上有一个读锁,其他人不能更改这些行。(*)
  • 计算您需要计算的内容,以确定是否需要更新某些内容
  • 如果需要,更新行
  • 承诺
  • 任何时候都可能出现错误。如果检测到死锁,MySQL可能会决定回滚事务以摆脱死锁。如果另一个事务正在更新您试图读取的行,则您的事务可能会被锁定一段时间,甚至超时
  • 如果以这种方式进行,事务的原子性将得到保证


    (*)通常,此选择未返回的行仍可以插入到并发事务中,也就是说,在整个事务过程中,不保证不存在该行,除非执行该操作。

    来自@YaK的答案实际上是一个很好的答案。一般来说,你应该知道如何处理锁

    具体地说,您的代码应该如下所示:

    $em->getConnection()->beginTransaction();
    try {
        $toUpdate = $em->find('Entity\WhichWillBeUpdated', $id,  \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
        // this will append FOR UPDATE http://docs.doctrine-project.org/en/2.0.x/reference/transactions-and-concurrency.html
        $em->persist($anInsertedOne);
        // you can flush here as well, to obtain the ID after insert if needed
        $toUpdate->changeValue('new value');
        $em->persist($toUpdate);
        $em->flush();
        $em->getConnection()->commit();
    } catch (\Exception $e) {
        $em->getConnection()->rollback();
        throw $e;
    }
    

    对于获取更新的每个后续请求,将等待一个已获取锁的进程完成此事务。Mysql将在事务成功或失败后自动释放锁。默认情况下,innodb锁定超时为50秒。因此,如果您的进程没有在50秒内完成事务,它将回滚并自动释放锁。实体上不需要任何其他字段。

    事务不会阻止线程B读取线程A未锁定的值。

    因此,必须使用锁来防止并发访问

    @Gediminas解释了如何将锁与条令一起使用。 但是使用锁可能会导致死锁或锁超时。 条令将这些SQL错误呈现为可重试的异常。 如果您处于高并发环境中,这些异常通常是正常的。 它们可能经常发生,您的应用程序应该正确处理它们

    每次条令引发RetryableException时,正确的处理方法是重试整个事务

    尽管看起来很容易,但有一个陷阱。条令2 EntityManager在发生RetryableException后变得不可用,您必须重新创建一个新的条令2 EntityManager以重播整个事务


    我写了一个完整的例子。

    您打算从一个表或多个表中读/写吗?如果您选择事务,这个问题会变得太大,在一般情况下无法解决。我最好的建议是:阅读大量关于并发的知识,了解MySQL锁/事务是如何工作的。您可能需要详细描述程序的逻辑(插入什么,在什么情况下更新哪一行)。@puty:对于许多编辑,这是非常有用的。我一开始误解了你最初的问题。我看不出你的问题其实很具体。