Doctrine (postgresql) пессимистическая блокировка - не генерирует исключение PessimisticLockException

Я пытаюсь использовать пессимистическую блокировку с Doctrine ORM для PostgreSql. Doctrine и PostgreSql с настройками по умолчанию (без каких-либо изменений).

Это пример кода (Symfony Command).

$sleep - это время в секундах

$manager = $this->getContainer()->get('mmi.manager.message');
$conn = $manager->em()->getConnection();

$manager->em()->getConnection()->beginTransaction();
try {
    $entity = $manager->repo()->find('cd7eb9e9', LockMode::PESSIMISTIC_WRITE);

    $entity->setState(EntityActionInterface::STATE_IN_PROGRESS);
    $manager->em()->persist($entity);
    $manager->em()->flush();

    $ts = (new \DateTime())->getTimestamp();
    $output->writeln("TS: {$ts}");

    if ($sleep) {
        $output->writeln("Sleep: {$sleep}");
        sleep($sleep);
    }

    $entity->setMessage([$ts]);
    $manager->em()->persist($entity);
    $manager->em()->flush();

    $conn->commit();
} catch (PessimisticLockException $ex) {
    var_dump(get_class($ex));

    $conn->rollBack();
    throw $ex;
} catch (\Exception $ex) {
    var_dump(get_class($ex));

    $conn->rollBack();
    throw $ex;
}

Как проверено

Запустите две команды. Первая команда выполняется с тайм-аутом 20 секунд. Вторая команда выполняется без тайм-аута.

Ожидаемый результат

Вторая команда бросков PessimisticLockException

Фактический результат

Вторая команда ожидает первого подтверждения транзакции, а затем обновляет строку.

Вопрос

Что я должен сделать, чтобы бросить Доктрину PessimisticLockException если строка сейчас заблокирована?

1 ответ

Решение

Для начала: как работает PESSIMISTIC_WRITE для платформы PostgreSql

PESSIMISTIC_WRITE - это запрос SELECT ... FOR UPDATE, Этот запрос блокирует выбранную строку и другие соединения, которые запрашивают ту же строку, ожидая, когда текущее соединение завершит свою работу.

В моем случае я запускаю два процесса, а второй ждет завершения первого. И это правильное поведение.

Моя ошибка: я изучаю исходный код Doctrine и нахожу PessimisticLockException учебный класс. Итак, я решаю, что Doctrine выдает это исключение, когда используется пессимистическая блокировка. Но этот класс нигде не используется в Учении.

Итак, как я решил эту проблему.

Моя текущая реализация требовала поведения nowait для заблокированных строк. И PostgreSql 9.5 имеет эту функцию - Пропуск заблокирован. Но Doctrine не имеет реализации для этой функции.

Что мы можем сделать?

Мы можем переопределить доктрину postgresql platfrom из класса.

use Doctrine\DBAL\Platforms\PostgreSqlPlatform;

class PgSqlPlatform extends PostgreSqlPlatform
{
    /**
     * Returns the FOR UPDATE expression.
     *
     * @return string
     */
    public function getForUpdateSQL()
    {
        return 'FOR UPDATE SKIP LOCKED';
    }
}

Определите это как услугу

#app/config/services.yml
services:
    mmi.dbal.pgsql_platform:
        class: {Namespace}\PgSqlPlatform

И установить конфиг доктрины

#app/config/config.yml
doctrine:
    dbal:
        connections:
            mmi:
                driver:   pdo_pgsql
                host:     ...
                ...
                platform_service: 'mmi.dbal.pgsql_platform'

Это все. Теперь мы можем использовать пессимистическую блокировку без ожидания.

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