【问题标题】:Doctrine2 ORM select for updateDoctrine2 ORM 选择更新
【发布时间】:2012-10-09 21:22:58
【问题描述】:

您能否建议一种如何使用 Doctrine 实现 SELECT FOR UPDATE 的方法?

我需要读取一个计数器值,然后在 PHP 代码中使用它,并在其他人(来自另一个进程)使用相同的值之前立即增加该值。

【问题讨论】:

    标签: php mysql concurrency doctrine


    【解决方案1】:

    显然,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);
    

    此外,您必须使用事务中的语句才能使其工作。

    【讨论】:

    • 我刚刚检查了我的 SQL 日志,当使用 PESSIMISTIC_WRITE 锁定模式时,Doctrine 2 确实使用了 SELECT FOR UPDATE
    • 刚刚重新检查了 Doctrine 源代码,是的,PESSIMISTIC_WRITE 实际上产生了 SELECT FOR UPDATE,很好!
    【解决方案2】:

    锁定支持

    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

    另外,你可以抛出执行原始 SQL:

    $em->getConnection()->exec('LOCK TABLES table_name WRITE;'); //lock for write access
    

    然后

    $em->getConnection()->exec('UNLOCK TABLES;');
    

    【讨论】:

    • MySQL 的 SELECT FOR UPDATE 与您的本地 SQL 解决方案完全不同:它使用“特殊”行级锁,该锁仅适用于其他 SELECT FOR UPDATE 语句(以及其他,请参阅 mysql 文档),并且使用交易不需要解锁(参见:dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html
    【解决方案3】:

    警告任何从谷歌来到这里的人。

    如果您在现有实体上使用 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-&gt;lock()$em-&gt;refresh() 都不能在这里工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-07
      • 2016-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-25
      • 2013-04-06
      相关资源
      最近更新 更多