put*_*uty 4 php mysql concurrency transactions doctrine-orm
我有一个应用程序,使用Doctrine2框架在php + mysql平台上运行.我需要在一个http请求期间执行3个db查询:第一个INSERT,第二个SELECT,第三个UPDATE.UPDATE取决于SELECT查询的结果.并发http请求的概率很高.如果发生这种情况,并且DB查询混淆(例如,INS1,INS2,SEL1,SEL2,UPD1,UPD2),则会导致数据不一致.如何确保INS-SEL-UPD操作的原子性?我需要使用某种锁,还是交易就足够了?
来自@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;
}
Run Code Online (Sandbox Code Playgroud)
获取更新的每个后续请求将等到此事务完成一个已获得锁定的进程.事务成功完成或失败后,Mysql将自动释放锁.默认情况下,innodb锁定超时为50秒.因此,如果您的进程在50秒内未完成事务,它将回滚并自动释放锁定.您的实体不需要任何其他字段.
全表范围LOCK保证在所有情况下都能工作。但它们非常糟糕,因为它们在某种程度上阻止了并发,而不是处理它。但是,如果您的脚本在很短的时间内持有锁,那么这可能是一个可接受的解决方案。
如果你的表使用InnoDB引擎(不支持MyISAM事务),事务是最高效的解决方案,但也是最复杂的。
对于您非常具体的需求(在同一个表中,第一个 INSERT,第二个 SELECT,第三个 UPDATE 取决于 SELECT 查询的结果):
如果您按照这种方式进行,则可以保证事务的原子性。
(*) 一般来说,此 SELECT未返回的行仍可能被插入到并发事务中,也就是说,在整个事务过程中不能保证不存在,除非采取适当的预防措施