EntityManager::lock() 什么时候有意义?

问题描述 投票:0回答:1

使用 Symfony 5.4 和 Doctrine,我有一个需要在考虑并发安全的情况下更新的实体。如果同时有 2 个请求,我需要向现有余额添加余额并避免过时的数据。

这是我的控制器:

#[Route('/{id}/add10ToBalance', name: 'cp_partners_resellers_edit')]
public function add10ToBalance(Request $request, Reseller $reseller): Response

正如您所看到的

Reseller
实体是从 URL 加载的。

既然我有了实体,我就:

$this->entityManager->getConnection()->beginTransaction(); // suspend auto-commit

try {
    $this->entityManager->lock($reseller, LockMode::PESSIMISTIC_WRITE);

    $reseller->setBalance($reseller->getBalance() + 10);

    $this->entityManager->persist($reseller);
    $this->entityManager->flush();

    $this->entityManager->getConnection()->commit();

我从调试器中的 SQL 日志确认,此问题按以下顺序发生:

SELECT * FROM reseller WHERE id = ?
START TRANSACTION
SELECT 1 FROM reseller t0 WHERE t0.id = ? FOR UPDATE
UPDATE reseller SET balance = ? WHERE id = ?
COMMIT

问题在这里似乎很清楚 -

lock()
函数实际上并没有刷新数据库中的数据。因此,在
SELECT
START TRANSACTION
之间,如果实体被修改,我现在正在数据库中使用锁定版本,但我的 Symfony 进程中的数据已过时。

显而易见的解决方案是使用

$reseller = $this->entityManager->find(Reseller::class, $reseller->getId(), LockMode::PESSIMISTIC_WRITE);

在顶部,这会导致正确的 SQL 顺序:

SELECT * FROM reseller WHERE id = ?
START TRANSACTION
SELECT * FROM reseller WHERE id = ? FOR UPDATE
UPDATE reseller SET balance = ? WHERE id = ?
COMMIT

但这让我想知道:

如果

EntityManager::lock()
方法会导致锁定潜在的过时数据,那么它的真正用途是什么?

php symfony concurrency doctrine
1个回答
0
投票

想象一下逻辑情况(它存在),您必须锁定一个实体,但数据修改是在其他一个或多个实体上完成的。

例如,B 实体的列表连接到一个 A 实体。您想为给定的 A 创建一个新的 B 实体,但无论是否允许,它都有复杂的业务逻辑。并且条件取决于给定 A 实体的 B 实体的现有列表。 您要做的操作如下。

  1. 您必须阅读与给定 A 实体相关的 B 实体列表。
  2. 您检查条件,如果您的新 B 适合此现有 B 项目列表,则步骤到 3。(否则返回不允许此操作的信息)
  3. 添加新的 B 实体(持续)

那么,在读取B列表和插入新B之间,任何人都不应该做同样的操作,因为如果在检查条件和写入新B的过程中出现了新B,那么检查结果就会失效。

那么你可以对什么进行锁定呢?您必须在给定的 A 实体上执行此操作,即使您实际上没有修改它。您只需插入一个新的 B 项目。而且这里你需要一个悲观锁。

这是 EntityManager::lock 方法不会导致潜在的陈旧数据的情况。

© www.soinside.com 2019 - 2024. All rights reserved.