【问题标题】:PHP, MySQL, PDO Transactions - Can fetchAll() come before commit()?PHP、MySQL、PDO 事务 - fetchAll() 可以在 commit() 之前出现吗?
【发布时间】:2016-09-18 05:07:09
【问题描述】:

更多交易问题!

我现在拥有的是一堆串在一起的查询,如果有任何失败,这些查询都会手动反转:

代码块 1

$stmt1 = $db->prepare(...); // Update table1, set col=col+1
if($stmt1 = $db->execute(...)){

    $stmt2 = $db->prepare(...); // Insert into table2, id=12345
    if($stmt2 = $db->execute(...)){

        $stmt3 = $db->prepare(...); // Select val from table3
        if($stmt3 = $db->execute(...)){

            $result = $stmt3->fetchAll();
            if($result[0]['val'] == something){

                $stmt4 = $db->prepare(...); // Update table4, set status=2
                if($stmt4 = $db->execute(...)){

                    return true;

                }else{
                    $stmt1 = $db->prepare(...); // Update table1, set col=col-1 (opposite of above)
                    $stmt1 = $db->execute(...);

                    $stmt2 = $db->prepare(...); // Delete from table2, where id=12345 (opposite of above)
                    $stmt2 = $db->execute(...);

                    return false;
                }
            }

            return true;
        }else{
            $stmt1 = $db->prepare(...); // Update table1, set col=col-1 (opposite of above)
            $stmt1 = $db->execute(...);

            $stmt2 = $db->prepare(...); // Delete from table2, where id=12345 (opposite of above)
            $stmt2 = $db->execute(...);

            return false;
        }
    }else{
        $stmt1 = $db->prepare(...); // Update table1, set col=col-1 (opposite of above)
        $stmt1 = $db->execute(...);

        return false;
    }
}

这是一团糟,难以调试,难以添加,当查询很大时难以理解,并且如果连接在中途丢失,则不会将所有表恢复到原始状态。

删除一行时,同样的过程更糟糕,因为其中的所有内容都需要存储 - 以防万一操作需要撤消。

现在,我知道大部分在我将其移植到单个事务时仍然可以使用,但我不确定的部分是:

代码块 2

$result = $stmt3->fetchAll();
if($result[0]['val'] == something){
    ... continue ...
}else{
    ... reverse operations ...
    return false;
}

因为结果收集将在事务中的commit() 之前进行。如下:

代码块 3

$db->beginTransaction();    

try{
    $stmt1 = $db->prepare(...);
    $stmt1->execute();

    $stmt2 = $db->prepare(...);
    $stmt2->execute();

    $stmt3 = $db->prepare(...);
    $stmt3->execute();

    $result = $stmt3->fetchAll();
    if($result[0]['val'] == something){
        $stmt4 = $db->prepare(...);
        $stmt4->execute();
    }else{
        $db->rollBack();
        return false;
    }

    $db->commit();

    return true;
}catch(Exception $e){
    $db->rollBack();
    throw $e;
    return false;
}

这行得通吗?具体来说,我可以在commit()之前包含$result = $stmt3->fetchAll();before,然后执行条件查询吗?

此外,我对此并不完全确定,但如果在 commit() 之前退出代码 (return false),我是否需要 try 块内的 $db->rollBack();

【问题讨论】:

    标签: php mysql pdo transactions


    【解决方案1】:

    你的第一个问题:

    具体来说,我可以包含 $result = $stmt3->fetchAll();在commit()之前,然后执行条件查询?

    我看不出它为什么不起作用。事务的行为与没有事务的操作基本相同 - 除了更改只是草稿。您在前面的语句中所做的任何更改都将应用于仅对该单个会话有效的“工作副本”。对你来说,它会显得完全透明。但是,如果您不提交,任何更改都将被回滚。

    同样值得注意(强调我的):

    通俗地说,事务中执行的任何工作,即使是分阶段执行,也保证安全地应用于数据库,并且不受其他连接的干扰,当它已提交。

    这可能会导致赛车状况。

    你的第二个问题:

    另外,我对此并不完全确定,但我是否需要 $db->rollBack();在 try 块内,如果代码在 commit() 之前退出(返回 false)?

    documentation 说:

    当脚本结束或连接即将关闭时,如果有未完成的事务,PDO 会自动回滚。

    因此您不一定需要手动回滚,因为它将由驱动程序自己完成。

    但请注意以下来自同一来源的内容:

    警告 PDO 仅检查驱动程序级别的事务功能。如果某些运行时条件意味着事务不可用,如果数据库服务器接受启动事务的请求,PDO::beginTransaction() 仍将返回 TRUE 而不会出错。

    所以一定要事先检查兼容性!

    一些注意事项

    不要在另一个事务中开始一个事务。这将隐式提交第一个事务。见this comment

    documentation 的另一条注释:

    当在事务中发出数据库定义语言 (DDL) 语句(例如 DROP TABLE 或 CREATE TABLE)时,包括 MySQL 在内的某些数据库会自动发出隐式 COMMIT。隐式 COMMIT 将阻止您回滚事务边界内的任何其他更改。

    【讨论】:

    • 感谢您的详尽回答。那么可以说让rollBack()处于失败状态以保证回滚会更安全/更好吗?另外,请仔细检查一下,您关于嵌套事务的最后一点只是一个警告 - 我没有在 代码块 3 中这样做吗?
    • 1) 我建议您包含手动回滚,因为它更易于阅读。熟记文档总是很不错的,但是阅读代码中您尝试尝试的内容总是更可取的。 2) 注释应视为警告。文档有点……不好指出这些东西,在我阅读该评论之前我遇到了奇怪的错误。
    • 对于隐式提交更准确地说:我碰巧连续调用了几个独立的函数,这些函数都必须调用 beginTransaction() (因为它们都可以被称为独立的)。每次调用 beginTransaction() 时都会提交前一个事务。我觉得最好的方法是构建一个包装类,它跟踪启动了多少事务,并且只有在所有事务都成功提交时才提交(一个简单的计数器套装)。
    • Yalon,关于您的最后一点(嵌套事务)-仅适用于同一数据库中的事务,对吗?我有两个不同的数据库,$dbu$dbb。我可以做类似$dbu->beginTransaction(); try{$dbb->beginTransaction(); try{}catch{}}catch{} 的事情,因为它们没有作用于同一个 PDO 对象吗?
    • 是的。该事务仅适用于它开始的会话。因此,如果您同时打开两个不同的会话,它们就会有两个不同的事务,它们不会相互干扰。
    猜你喜欢
    • 2016-09-18
    • 1970-01-01
    • 2016-10-05
    • 1970-01-01
    • 2021-12-26
    • 2014-11-05
    • 1970-01-01
    • 2014-01-23
    • 2011-12-24
    相关资源
    最近更新 更多