PDO嵌套事务导致PHP挂起

时间:2014-02-24 20:53:18

标签: php transactions

在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本身应该能够通过简单地跟踪嵌套事务来正确处理这个问题。

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

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:0)

如果你真的打算A()和B()分别在他们自己的交易中,那么这在任何级别都是无法解决的。你只是编写了一个保证死锁的代码。

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

如果你根本不关心B()是否在一个事务中,至少在这种情况下,那么你需要某种关于事务嵌套的应用程序级内省。 B()必须检查并遵守这一点。但PDO不能这样做,除非你把它包装成包含至少一些单例功能。但我认为你想要在交易中使用B()。你只是希望它在A()的事务中,这样就没有死锁。

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