简体   繁体   中英

Doctrine2 ORM select for update

Could you suggest an approach how to implement SELECT FOR UPDATE with Doctrine?

I need to read a counter value, then use it in PHP code and immediately increment the value before someone else (from another process) uses the same value.

Apparently, Doctrine 2 uses LOCK IN SHARED MODE with Pessimistic read lock for MySQL, which is not the same as SELECT FOR UPDATE.

Looking at the sources of the current stable release, it seems that there is no native way of doing so in Doctrine (I'm not sure why the Doctrine team chose that type of lock for MySQL).

I used native SQL as workaround, which can be mapped to the traditional entities, as it would be with 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();

Update

As Benjamin has pointed out, PESSIMISTIC_WRITE is what you are looking for.

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

Without DQL

<?php
$entity = $em->find('Application\Model\Entity\MyEntity', $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);

Also, you have to use the statement inside a transaction to make it work.

Locking support

Doctrine 2 implements Locking support for entities:

<?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!";
}

Native sql

Also, you can do it throws execute raw SQL:

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

and then

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

Warning for anyone coming here from google.

If you use Doctrine's PESSIMISTIC_WRITE lock on existing entity,
then that entity will not be refetched after locking.

So this code:

$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();

Will run something similar to following code in 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

This gives the same result as not locking anything at all.

You need to manually fetch the entity again while requesting the lock at the same time:

$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();

This is the only way to make sure doctrine will update its cache as well as the entity object itself after lock is acquired.

Neither $em->lock() , nor $em->refresh() will work here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM