PHP PDO MySQL事务代码结构

7

我正在尝试使用PHP/PDO在MySQL中设置我的第一个事务...

我有一个快速问题,最好的方法是什么来确定前面的查询是否成功? 这就是我现在所拥有的,但我更愿意通过if语句来测试查询。

这基本上是虚拟代码,以尝试获得工作模型..我知道$results并没有有效地测试任何好坏的东西..当时机成熟时,我将它放在那里作为真正交易的占位符。

if ($_POST['groupID'] && is_numeric($_POST['groupID'])) {
    $sql = "SET AUTOCOMMIT=0";
    $dbs = $dbo->prepare($sql);
    $dbs->execute();

    $sql = "START TRANSACTION";
    $dbs = $dbo->prepare($sql);
    $dbs->execute();

    $sql = "DELETE FROM users_priveleges WHERE GroupID=:groupID";
    $dbs = $dbo->prepare($sql);
    $dbs->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
    $dbs->execute();

    try {
        $sql = "DELETE FROM groups WHERE GroupID=:groupID LIMIT 1";
        $dbs = $dbo->prepare($sql);
        $dbs->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
        $dbs->execute();

        $results["error"] = null;
        $results["success"] = true;

        try {
            $sql = "DELETE FROM users WHERE Group=:groupID";
            $dbs = $dbo->prepare($sql);
            $dbs->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
            $dbs->execute();

            $results["error"] = null;
            $results["success"] = true;

            $sql = "COMMIT";
            $dbs = $dbo->prepare($sql);
            $dbs->execute();
        }
        catch (PDOException $e) {
            $sql = "ROLLBACK";
            $dbs = $dbo->prepare($sql);
            $dbs->execute();

            $results["error"] = "Could not delete associated users! $e";
            $results["success"] = false;
        }   
    }
    catch (PDOException $e)
    {
        $sql = "ROLLBACK";
        $dbs = $dbo->prepare($sql);
        $dbs->execute();

        $results["error"] = "COULD NOT REMOVE GROUP! $e";
        $results["success"] = false;
    }
}

2
为什么不使用PDO的beginTransaction()、commit()和rollback()方法呢? - GordonM
LOL 今天早上我刚学到了beginTransaction方法... 我想另外两个也在那里,但还没有查过。不过这已经在待办事项清单上了,谢谢! - guyfromfl
此外,您不必为每个语句都准备(prepare())一次,事实上,对于那些您不打算插入任何变量的语句来说,这样做是相当浪费的。只需使用query()运行它们即可。这样既可以节省代码行数,也可以避免不必要的准备操作。 - GordonM
3个回答

24
一些常规注意事项: 除非使用修改参数值的过程,否则不要使用bindParam()。 因此,请使用bindValue()。 bindParam()将参数值作为引用变量接受参数。这意味着您不能执行$ stmt -> bindParam(':num',1,PDO :: PARAM_INT);——它会引发错误。 此外,PDO具有自己的控制事务功能,无需手动执行查询。
我稍微改写了您的代码,以阐明如何使用PDO。
if($_POST['groupID'] && is_numeric($_POST['groupID']))
{
    // List the SQL strings that you want to use
    $sql['privileges']  = "DELETE FROM users_priveleges WHERE GroupID=:groupID";
    $sql['groups']      = "DELETE FROM groups WHERE GroupID=:groupID"; // You don't need LIMIT 1, GroupID should be unique (primary) so it's controlled by the DB
    $sql['users']       = "DELETE FROM users WHERE Group=:groupID";

    // Start the transaction. PDO turns autocommit mode off depending on the driver, you don't need to implicitly say you want it off
    $pdo->beginTransaction();

    try
    {
        // Prepare the statements
        foreach($sql as $stmt_name => &$sql_command)
        {
            $stmt[$stmt_name] = $pdo->prepare($sql_command);
        }

        // Delete the privileges
        $stmt['privileges']->bindValue(':groupID', $_POST['groupID'], PDO::PARAM_INT);
        $stmt['privileges']->execute();

        // Delete the group
        $stmt['groups']->bindValue(":groupID", $_POST['groupID'], PDO::PARAM_INT);
        $stmt['groups']->execute();

        // Delete the user 
        $stmt['users']->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
        $stmt['users']->execute();

        $pdo->commit();     
    }
    catch(PDOException $e)
    {
        $pdo->rollBack();

        // Report errors
    }    
}

哇,感谢bindParam/Value的信息...我读过的所有PDO教程都使用bindParam..那段代码看起来很棒,我会试着改进你的想法,这样做非常有道理。 - guyfromfl
当尝试删除该组中的用户时,我遇到了一个错误。错误信息显示在Group=38附近...另外两个查询仍然可以删除其他表中的行,似乎事务没有正常工作... - guyfromfl
GROUP是SQL保留字,因此您可能需要将其放在反引号中。但最好将其重命名,以便它不是保留字,并且所有表都一致地将其命名为GroupID - Bill Karwin

7

我不会准备和执行事务语句。我会使用PDO::beginTransaction(), PDO::commit(), 和 PDO::rollback()

如果出现错误,PDO::prepare() 和 PDO::execute() 会返回 FALSE, 否则它们会抛出 PDOException,如果你 setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)

在异常处理程序中,你应该检查 PDO::errorInfo() 并报告错误的性质。最佳实践是记录原始错误信息,但给用户一个更友好的消息。

不要在UI中回显字面错误消息 - 这可能会让用户了解你的SQL查询和模式。


是的,我刚学习了一些关于事务方法的知识。我想用if语句来控制它的主要原因是这样更容易,因为我的经验有限,可以更容易地捕获特定的错误。我也读到过,对于我正在做的事情,try不是理想的选择,原因有很多。谢谢你的信息,还有很多需要研究的地方! - guyfromfl
我刚刚仔细检查了,setAttribute选项设置为在我实例化$dbo对象时传递,所以我们没问题了。感谢反馈。 - guyfromfl

4

PDO语句的execute()函数在成功时返回TRUE,在失败时返回FALSE,因此您可以在if语句中测试前一个execute()的返回值。

$pdo_result = $dbs->execute();
if ($pdo_result) {
    // handle success
} else {
    // handle failure
    // you can get error info with $dbs->errorInfo();
}

话虽如此,正如 @Bill Kerwin 所指出的(在他的回答中我完全支持并点赞,因为它是完全正确的),最好使用 PDO::beginTransaction()PDO::commit()PDO::rollback()


我该如何知道是什么导致了错误?我想让它对问题做出响应。抱歉,第一次使用PDO还有点新手...感谢回复。 - guyfromfl
1
$dbs->errorInfo() 查看文档 http://www.php.net/manual/en/pdostatement.errorinfo.php 以了解其返回值的解释。 - Trott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接