Doctrine (postgresql) Pessimistic Locking - 不会抛出 PessimisticLockException

Doctrine (postgresql) Pessimistic Locking - doesn't throw PessimisticLockException

我尝试将悲观锁与 Doctrine ORM 一起用于 PostgreSql。 具有默认配置的 Doctrine 和 PostgreSql(没有任何更改)。

这是代码示例(Symfony 命令)。

$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

实际结果

第二个命令等待第一个事务提交,然后更新行。

问题

如果行现在被锁定,我应该怎么做才能让 Doctrine 抛出 PessimisticLockException

首先: PostgreSql 平台的工作方式PESSIMISTIC_WRITE

PESSIMISTIC_WRITE - 这是查询 SELECT ... FOR UPDATE。此查询锁定所选行和请求同一行的其他连接,等待当前连接完成它的工作。

在我的例子中,我启动了两个进程,第二个等待完成第一个。这是正确的行为。

我的错误: 我正在探索 Doctrine 源代码并找到 PessimisticLockException class。所以,我决定 Doctrine 在使用悲观锁时抛出这个异常。但是这个 class 没有在 Doctrine 的任何地方使用。

那么,我是如何解决这个问题的。

我当前的实现需要锁定行的 nowait 行为。并且 PostgreSql 9.5 具有此功能 - SKIP LOCKED。但是 Doctrine 没有这个特性的实现。

我们能做什么?

我们可以覆盖学说 postgresql 平台class。

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

并设置 tot 学说配置

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

就是这样。现在我们可以不用等待就可以使用悲观锁了