【发布时间】:2012-10-09 21:22:58
【问题描述】:
您能否建议一种如何使用 Doctrine 实现 SELECT FOR UPDATE 的方法?
我需要读取一个计数器值,然后在 PHP 代码中使用它,并在其他人(来自另一个进程)使用相同的值之前立即增加该值。
【问题讨论】:
标签: php mysql concurrency doctrine
您能否建议一种如何使用 Doctrine 实现 SELECT FOR UPDATE 的方法?
我需要读取一个计数器值,然后在 PHP 代码中使用它,并在其他人(来自另一个进程)使用相同的值之前立即增加该值。
【问题讨论】:
标签: php mysql concurrency doctrine
显然,Doctrine 2 使用 LOCK IN SHARED MODE 和 MySQL 的悲观读锁,这与 SELECT FOR UPDATE 不同。
看目前stable release的源码,好像没有native方式 在 Doctrine 中这样做(我不确定 Doctrine 团队为什么为 MySQL 选择这种类型的锁)。
我使用原生 SQL 作为解决方法,它可以映射到传统实体,就像使用 DQL 一样:
<?php
$rsm = new ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata('Model_Record_Delivery', 'u');
$query = $this->_em->createNativeQuery("SELECT * FROM delivery WHERE id = :id FOR UPDATE", $rsm);
$query->setParameter("id", $id);
$result = $query->getOneOrNullResult();
正如 Benjamin 所指出的,PESSIMISTIC_WRITE 正是您要寻找的。p>
使用 DQL
<?php
$query = $this->em->createQuery('SELECT e
FROM Application\Model\Entity\MyEntity e
WHERE e = :id');
$query->setParameter("id", $id);
$query->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
没有 DQL
<?php
$entity = $em->find('Application\Model\Entity\MyEntity', $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
此外,您必须使用事务中的语句才能使其工作。
【讨论】:
PESSIMISTIC_WRITE 锁定模式时,Doctrine 2 确实使用了 SELECT FOR UPDATE。
Doctrine 2 为实体实现 Locking support:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Someone else has already changed this entity. Apply the changes again!";
}
另外,你可以抛出执行原始 SQL:
$em->getConnection()->exec('LOCK TABLES table_name WRITE;'); //lock for write access
然后
$em->getConnection()->exec('UNLOCK TABLES;');
【讨论】:
警告任何从谷歌来到这里的人。
如果您在现有实体上使用 Doctrine 的 PESSIMISTIC_WRITE 锁,
那么该实体在锁定后将不会被重新获取。
所以这段代码:
$entity = $this->em->find(Product::class, $id);
// use the product for some read only code
// Later, Need to update product
$this->em->lock($entity, LockMode::PESSIMISTIC_WRITE);
$entity->setStock($entity->getStock() - 1);
$this->em->flush();
将在 SQL 中运行类似于以下代码的内容
SELECT t0.id AS id_1, t0.stock AS stock_2 FROM products t0 WHERE t0.id = ?; -- First fetch
SELECT 1 FROM products t0 WHERE t0.id = ? FOR UPDATE; -- Pessimistic lock, no data fetched
UPDATE products SET stock = ? WHERE id = ?; -- Update using old data
这与根本不锁定任何东西的结果相同。
你需要在请求锁的同时再次手动获取实体:
$entity = $this->em->find(Product::class, $id);
// use the product for some read only code
// Need to update product
$this->em->find(Product::class, $entity->getId(), LockMode::PESSIMISTIC_WRITE); // You dont need the return value, doctrine will update all loaded entities
$entity->setStock($entity->getStock() - 1);
$this->em->flush();
这是确保在获取锁后,学说将更新其缓存以及实体对象本身的唯一方法。
$em->lock() 和 $em->refresh() 都不能在这里工作。
【讨论】: