5

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

4

3 回答 3

8

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

特别针对 Doctrine2,您的代码应如下所示:

$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 秒内完成事务,它将自动回滚并释放锁。您不需要实体上的任何其他字段。

于 2013-04-29T04:33:44.117 回答
2

保证表范围LOCK在所有情况下都可以工作。但它们非常糟糕,因为它们有点防止并发,而不是处理它。但是,如果您的脚本在很短的时间内持有锁,这可能是一个可以接受的解决方案。

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

对于您非常具体的需求(在同一个表中,第一个 INSERT,第二个 SELECT,第三个 UPDATE 取决于 SELECT 查询的结果):

  1. 开始交易
  2. 插入您的记录。在提交您自己的事务之前,其他事务不会看到这些新行(除非您使用非标准隔离级别
  3. 使用SELECT...LOCK IN SHARE MODE选择您的记录。您现在对这些行有一个 READ 锁,其他人不能更改这些行。(*)
  4. 计算您需要计算的任何内容以确定您是否需要更新某些内容。
  5. 如果需要,更新行。
  6. 犯罪
  7. 随时期待错误。如果检测到死锁,MySQL 可能会决定回滚您的事务以逃避死锁。如果另一个事务正在更新您尝试从中读取的行,则您的事务可能会被锁定一段时间,甚至超时。

如果您以这种方式进行,则可以保证您的交易的原子性。

(*) 一般而言,此 SELECT返回的行仍可能插入到并发事务中,也就是说,除非采取适当的预防措施,否则在整个事务过程中不能保证不存在

于 2012-06-15T15:25:38.057 回答
1

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

所以你必须使用锁来防止并发访问。

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

每次 Doctrine 抛出 RetryableException 时,处理此问题的正确方法是重试整个事务。

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

我用一个完整的例子写了这篇文章。

于 2018-10-10T08:32:35.233 回答