【问题标题】:PDO nesting transactions causes PHP to hangPDO 嵌套事务导致 PHP 挂起
【发布时间】:2014-03-26 18:22:45
【问题描述】:

在 PHP 中,使用内置的 PDO 数据库抽象层,这样的代码并不少见:

function B()
{
    $db = new PDO(...);
    $db->beginTransaction();

    // do something

    $db->commit();
}

function A()
{
    $db = new PDO(...);
    $db->beginTransaction();

    // do something

    B();

    $db->commit();
}

请注意,A 和 B 都有自己的数据库连接,并且都启动事务,并且 A 调用 B。结果是,在某些情况下(即 B 正在做的事情依赖于 A 所做的事情),这种编码模式将导致死锁。

例如,假设我们有表 FooBarFoo 包含引用 Bar 的外键,并设置为 ON DELETE CASCADE。假设函数A 更新Foo 以更改该外键字段的值,然后函数B 删除Bar 中现在未使用的记录。因为A 的事务尚未提交,B 中的 SQL 将等待它,即使在 B 返回之前它永远不会提交。 有没有办法在不将数据库对象传递给每个函数的情况下解决这个问题?看起来 PDO 本身应该能够通过简单地跟踪嵌套事务来正确处理这个问题。

我已经尝试了 cmets here 中的解决方案,其中 PDO 被子类化以进行嵌套事务计数,但它似乎并没有真正起作用 - 我假设因为 A() 和 @987654338 中的 $db 对象@ 不是同一个实例,所以它们彼此不知道,并且任一实例中的计数器的值都不正确。

【问题讨论】:

  • “这并不罕见”?真的吗?
  • 当然,为什么不呢?这似乎是最明显的实现。例如,PHP docs 似乎正是这样做的 - 请参阅“示例 #2”。你会建议什么?

标签: php transactions


【解决方案1】:

您实际上应该只使用一个 PDO 实例。你需要弄清楚如何传递它。有很多方法。直接传递它,单例,全局函数。选择一种方法并使用它。

【讨论】:

  • 这在我看来像是 PDO 连接池代码应该处理的事情。此外,我在文档中看不到任何地方说一次只能存在一个实例。您是否有一些参考资料表明这是设计事物的正确方法?
【解决方案2】:

如果您真的希望 A() 和 B() 都在各自的事务中,那么这在任何级别都是无法解决的。您只是编写了保证死锁的代码。

如果你不在乎 B() 是否在它自己的事务中,只要它在some事务中,那么你仍然有一个不可能的情况。 B() 不能在 A() 的事务中,因为事务不跨越连接。但是 B() 不能在一个单独的事务中而不导致死锁问题。因此,B() 不能在任何事务中。

如果您根本不关心 B() 是否在事务中,至少在这种情况下,那么您将需要某种关于事务嵌套的应用程序级自省。 B() 必须检查并遵守这一点。但是 PDO 无法做到这一点,除非您将其包装为至少包含一些单例功能。但我认为您确实希望 B() 在交易中。您只希望它在 A() 的事务中,这样就不会出现死锁。

在这种情况下,唯一好的解决方案是 Damien 的解决方案,尽管您仍然需要使用其中一个跟踪嵌套事务的 PDO 包装器。这是让两个函数在独立调用时启动事务的唯一方法,但要知道只有第一个函数在以嵌套方式调用时才会启动事务。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-26
    • 1970-01-01
    • 1970-01-01
    • 2015-06-25
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    • 2013-03-24
    相关资源
    最近更新 更多