【问题标题】:How to lock a whole table in symfony2 with doctrine2?如何在 symfony2 中用学说 2 锁定整个表?
【发布时间】:2012-06-29 04:43:35
【问题描述】:

我需要用原则锁定整个表(不是单行),如果可能的话,我想在没有本地查询的情况下这样做。

pessimistic locking 的文档仅描述了如何通过这些方法锁定特定实体:

  • EntityManager#find
  • EntityManager#lock
  • 查询#setLockMode

我有一个事务需要插入一行,其值取决于表中其余行的值,因此我需要防止两个事务同时在该表上执行。

我正在使用显式事务划分,它应该可以很好地与锁定配合使用(根据上面的文档)。

注意:在这种情况下,乐观锁定不够好,我无法重试事务。此外,查询不应该很慢,因此性能不是问题。

编辑:我举个例子。想象一下,您想手动构建一个 auto_increment,并且您必须从表中选择 max() 以获取上一个结果才能插入下一个结果。您必须确保没有两个事务尝试插入相同的值,以防它们同时选择 max()。

当 auto_increment 不好时,我正在寻找此问题的通用解决方案,例如使用字符串、多列、散列或您必须对前一个行集进行的任何计算。

锁定是一种可靠的解决方案,与乐观锁定不同,您不必重试错误。

那么,有没有什么方法可以在学说中使用表锁定?

【问题讨论】:

  • Imo,更好的选择是打开一个事务,通过选择查询从您需要的表中获取数据,然后进行更新。
  • 问题是如果第二个事务在第一个提交之前出现,那么第二个事务将插入一个无效值。第二个事务的结果也应该取决于第一个事务插入的行。这就是我需要锁定的原因,该表的事务不应重叠。

标签: mysql symfony locking doctrine-orm


【解决方案1】:

到目前为止,我按照建议尝试了这个:

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

// calculate $new_number...

// persist $new_number on table_name...
$table_name->setCalculatedNumber($new_number);
$em->persist($table_name);
$em->flush();

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

我使用 JMeter 对其进行了测试,但在负载过重(16 个请求/秒)的情况下锁定无法正常工作。跟踪显示其他实例在明确放弃之前获得了锁。问题(正如 Jens 所建议的)是 flush() 隐式以 START TRANSACTION 开头,这会删除表锁。 使用本机更新为我解决了这个问题:

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

// calculate $new_number...

// persist $new_number on table_name...
$em->getConnection()->executeUpdate("UPDATE table_name set ...;");    

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

需要尾随 refresh() 以使计算出的数字在后续查询结果中可用

【讨论】:

    【解决方案2】:

    可能被Doctrine2 ORM select for update复制

    下面是一些相关代码:

    try {
        $entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
    
        // do the work
    
        $em->flush();
    } catch(OptimisticLockException $e) { 
        echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
    }
    

    LockMode::OPTIMISTIC 参数可能会提供您需要的内容。

    【讨论】:

    • 一般来说,选择行进行更新不会阻止并发插入。
    【解决方案3】:

    通过查看 Doctrine 2.x 中的文档,我认为没有支持锁定整个表的方法。您当然可以尝试通过 Doctrine 单独锁定所有记录,但这会很麻烦并且不是一个好主意。

    相反,我会使用 Doctrine 实体管理器在数据库上执行原始 SQL...

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

    然后在您完成更新后...

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

    编辑:

    来自MySQL documentation 表锁...

    • 持有锁的会话可以读写表。

    • 只有持有锁的会话才能访问表。在锁被释放之前,没有其他会话可以访问它。

    • 在持有 WRITE 锁时,其他会话对表的锁定请求会阻塞。

    我认为这里的第二点很关键,只有您的会话可以读取/写入该表。

    【讨论】:

    • 另外,单独锁定记录不会阻止新记录的插入。 OTOH 根据文档,LOCK 语句不是事务性的,我不确定学说如何实现事务,但如果它使用 START TRANSACTION 它将无法工作。
    • LOCK TABLES [tbl_name] 将锁定整个表进行写入,请参阅我的编辑
    • 是的,我知道,我对 LOCK 的唯一问题是,它只有在您使用 'autocommit = 0' 进行事务时才能正常运行,而且我不确定教义如何处理事务。我仍然需要测试它。看到这个dev.mysql.com/doc/refman/5.0/en/…
    • Doctrine 确实要求您将调用包装在显式事务中,即使这样也很困难(不可能?)因为教义有时在其 SQL 更新中使用别名(“如果您的语句通过某种方式引用表的别名,您必须使用相同的别名锁定表”)。在我的情况下,它在一个事务中使用别名和表名。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    • 2013-04-20
    • 1970-01-01
    • 2014-02-21
    • 2012-06-15
    • 1970-01-01
    相关资源
    最近更新 更多