Doctrine2 ORM выберите для обновления

Пожалуйста, не могли бы вы предложить мне какой-то подход, как реализовать SELECT FOR UPDATE с доктриной?

Мне нужно прочитать какое-то значение счетчика, использовать его в коде PHP и немедленно увеличить значение, прежде чем кто-то другой (из другого процесса) использует это же значение.

3 ответа

Решение

Блокировка поддержки

В Doctrine 2 реализована поддержка блокировки для сущностей:

<?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

Кроме того, вы можете сделать это с помощью execute raw SQL:

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

а потом

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

По-видимому, Doctrine 2 использует LOCK IN SHARED MODE с пессимистической блокировкой чтения для MySQL, которая отличается от SELECT FOR UPDATE.

Глядя на источники текущей стабильной версии, кажется, что в 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();

Обновить

Как отметил Бенджамин, PESSIMISTIC_WRITE - это то, что вы ищете.

С 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);

Кроме того, вы должны использовать инструкцию внутри транзакции, чтобы заставить ее работать.

Предупреждение для всех, кто приходит сюда из Google.

Если вы используете блокировку 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()здесь будет работать.

Другие вопросы по тегам