一次使用多个 PDO 准备好的语句

asm*_*ian 2 php mysql pdo

这是一个基于6 年前 PHP.net PDO::prepare 手册中很容易被遗漏的评论的确认/澄清请求,我在其他地方没有看到过讨论(甚至在伟大的 phpdelusions 博客中)。如果这是真的,那么它的可能性就非常大,我觉得它值得更广泛的报道和搜索突出显示给其他人(如果不是,则需要揭穿)。

这是评论(海莉·沃森):

可以针对单个连接提前准备多个语句。只要该连接保持打开状态,就可以按照您喜欢的顺序以任意顺序执行和获取语句;他们的“准备-执行-获取”步骤可以以最好的方式交错进行。

因此,如果您可能经常使用多个语句(可能在事务循环内),您可能需要考虑预先准备要使用的所有语句。

我有一些代码必须像(伪代码)一样运行:

foreach (fetchAll row with PDO) {
    process row results
    if (condition)
        update table with processed results
    else
        delete row no longer needed
}
Run Code Online (Sandbox Code Playgroud)

根据该评论,我可以在循环之前创建两个准备好的语句,一个用于更新查询,一个用于删除查询,然后(仅)在循环内执行。只要句柄不同并保留,连接就应该缓存两者,我可以互换使用它们,并且不必在循环内进行任何 SQL 语句解析,这将非常低效:

// do statement prepare/execute/fetchAll for main loop, then...
$update_stmt = $PDO->prepare($update_query);
$delete_stmt = $PDO->prepare($delete_query);
foreach (fetchAll row) {
    process row results
    if (condition)
        $update_stmt->execute(some_processed_values);
    else
        $delete_stmt->execute(some_other_values);
}
Run Code Online (Sandbox Code Playgroud)

由于这里的大多数问题只讨论一次使用一个准备好的语句,如果广泛应用,这对代码效率有很好的影响,有人想确认这确实是这样吗(至少从 PHP7 开始)?如果是这样,我想这种形式的代码的其他简洁应用程序可以在解决方案中共享。

Dha*_*man 5

同时使用多个准备好的语句并乱序执行它们是没有问题的。

您可以使用交织的语句运行如下代码,它将起作用。

$stmt1 = $pdo->prepare('INSERT INTO addplate(Plate) VALUES(?)');
$stmt2 = $pdo->prepare('UPDATE addplate SET Plate=? WHERE Plate=?');

$stmt1->execute(['val1']);
$stmt2->execute(['val2', 'val1']);
$stmt1->execute(['val1']);
$stmt2->execute(['val2', 'val1']);
$stmt1->execute(['val1']);
Run Code Online (Sandbox Code Playgroud)

当由于某种原因您无法避免 N+1 问题时,这可以为您带来一些性能优势。您准备一次内部查询,然后在循环内多次执行它。

但是,如果您想运行无缓冲查询(很少使用),这可能会成为生成结果的查询的问题。PDO 默认情况下执行缓冲查询,因此您需要将其关闭才能遇到此问题。

$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

$stmt1 = $pdo->prepare('SELECT * FROM addplate WHERE Plate=?');
$stmt2 = $pdo->prepare('SELECT * FROM addplate WHERE Plate=?');

$stmt1->execute(['val1']);
var_dump($stmt1->fetch());
$stmt2->execute(['val2']); // <-- Error if stmt1 still has more records
var_dump($stmt2->fetch());
Run Code Online (Sandbox Code Playgroud)

它将产生:

致命错误:未捕获的 PDOException:SQLSTATE[HY000]:一般错误:2014 当其他未缓冲的查询处于活动状态时无法执行查询。考虑使用 PDOStatement::fetchAll()。或者,如果您的代码仅针对 mysql 运行,您可以通过设置 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 属性来启用查询缓冲。