当我在php中使用mysql与mysqli的事务时,multi_query不起作用

时间:2016-10-17 13:43:07

标签: php mysqli multi-query

以下是我的代码示例

$mysqli = new mysqli("localhost", "root", "123", "temp");

var_dump($mysqli);

$mysqli->begin_transaction();

$sql1 = "insert into test (Name) values ('pratik5');";

$sql1 .= "insert into test (Name) values ('pratik6');";

$test = $mysqli->multi_query($sql1);

$mysqli->commit();

3 个答案:

答案 0 :(得分:3)

$mysqli->multi_query($sql1);
$mysqli->commit(); // This will result in a "Commands out of sync; you can't run this command now" error.

以上内容与

相同:
$mysqli->multi_query($sql1);
$mysqli->query("commit"); // This will result in a "Commands out of sync; you can't run this command now" error.

无论您输入什么$mysqli->query("...");,即使使用简单的"Commands out of sync",它也会导致SELECT 1错误;

此错误的原因是因为->commit()操作运行单个查询(commit;)。但是,以前的查询的结果尚未读取。

当使用单个query()操作时,MySQL服务器将使用取决于查询语句的响应帧进行应答。

使用multi_query()时,在MySQL通信协议级别发生以下情况:

  1. 发送“请求集选项”(如Wireshark所示)帧,并将“多条语句”标志设置为 ON
  2. 包含整个字符串的帧,该字符串根据请求传输到multi_query()
  3. MySQL服务器回答可能包含不同结果集的响应。调用存储过程时也是如此。

解决方案1 ​​

如果要使用multi_query(),则必须将start transaction / commit操作作为其一部分:

$mysqli = new mysqli("localhost", "root", "123", "temp");

$sql1 = "start transaction;"; // $mysqli->begin_transaction() is a convenience function for simply doing this.
$sql1 .= "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$sql1 .= "commit;"; // $mysqli->commit() is a convenience function for simply doing this.

$mysqli->multi_query($sql1);

/* As in "Solution 2", if you plan to perform other queries on DB resource
   $mysqli after this, you must consume all the resultsets:
// This loop ensures that all resultsets are processed and consumed:
do {
    $mysqli->use_result();
}
while ($mysqli->next_result());
*/

解决方案2

$mysqli = new mysqli("localhost", "root", "123", "temp");

$mysqli->begin_transaction();

$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";

$mysqli->multi_query($sql1);

// This loop ensures that all resultsets are processed and consumed:
do {
    $mysqli->use_result();
}
while ($mysqli->next_result());

// Now that all resultsets are processed, a single query `commit;` can happen:
$mysqli->commit();

MySQL参考:"Commands out of sync"

答案 1 :(得分:1)

您不应该使用多重查询。重写您的代码如下

$mysqli->begin_transaction();

$mysqli->query("insert into test (Name) values ('pratik5')");
$mysqli->query("insert into test (Name) values ('pratik6')");

$mysqli->commit();

或者,对于现实插页,

$mysqli->begin_transaction();

$stmt = $mysqli->prepare("insert into test (Name) values (?)");
$stmt->bind_param("2", $name);
$name = 'pratik5';
$stmt->execute();
$name = 'pratik6';
$stmt->execute();

$mysqli->commit();

答案 2 :(得分:0)

首先,在示例中您显示了您不应该使用multi_query() 。您有两个单独的语句,应分别执行。参见Your Common Sense's answer

multi_query()应该很少使用。仅当您已经拥有一个由多个查询组成的字符串时,该字符串才是您绝对信任的情况,才应使用它。永远不要允许变量输入multi_query()

为什么commit()multi_query()之后不起作用?

实际上,MySQL确实在commit()上抛出了错误,但是mysqli不能将错误作为异常抛出。我不知道是错误还是技术限制。如果手动检查$mysqli->error属性,则会看到错误。您应该看到如下错误:

命令不同步;您现在不能运行此命令

但是,一旦调用use_result()store_result(),就会正确引发异常。

$mysqli->multi_query(/* SQLs */);
$mysqli->commit();
$r = $mysqli->use_result(); // Uncaught mysqli_sql_exception: Commands out of sync; you can't run this command now

MySQL之所以说命令不同步,是因为每个MySQL会话一次只能执行一个命令。但是,multi_query()通过单个命令将整个SQL字符串发送给MySQL,让我们让MySQL异步运行查询。在将控件传递回PHP脚本之前,PHP最初将等待第一个产生非DML查询(非INSERT,UPDATE或DELETE)的结果集完成运行。这使MySQL可以在服务器上运行其余的SQL并缓冲结果,而您可以使用PHP进行其他一些操作并稍后收集结果。在迭代以前的异步查询的所有结果之前,您不能在同一连接上运行另一个SQL命令。

正如另一个答案所指出的,commit()将尝试在同一连接上执行另一个命令。之所以出现不同步错误,是因为您还没有完成处理multi_query()命令。

MySQLi阻止循环

每个异步查询都应在mysqli中紧跟一个阻塞循环。阻塞循环将遍历服务器上所有已执行的查询,并一步一步地获取结果,然后可以在PHP中进行处理。这是此类循环的示例:

do {
    $result = $mysqli->use_result();
    if ($result) {
        // process the results here
        $result->free();
    }
} while ($mysqli->next_result()); // Next result will block and wait for next query to finish
$mysqli->store_result(); // Needed to fetch the error as exception

即使您知道查询不会产生任何结果集,也必须始终具有阻塞循环。

解决方案

远离multi_query()!大多数时候,有一种更好的执行SQL文件的方法。如果您将查询分开,则不要将它们串联在一起,而是自己执行。

如果您确实需要使用multi_query(),并且希望将其包装在事务中,则必须将commit()放在阻塞循环之后。您必须先迭代所有结果,然后才能执行COMMIT;命令。

$mysqli->begin_transaction();

$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";

$test = $mysqli->multi_query($sql1);

// $mysqli->commit();

do {
    $result = $mysqli->use_result();
    if ($result) {
        // process the results here
        $result->free();
    }
} while ($mysqli->next_result());
$mysqli->store_result(); // To fetch the error as exception

$mysqli->commit();

当然要查看任何mysqli错误,您需要启用例外模式。只需将这一行放在new mysqli()之前:

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);