繁体   English   中英

为什么使用事务时 multi_query 不起作用?

[英]Why does multi_query not work when I use transactions?

这是一个例子。

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

$mysqli->begin_transaction();

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

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

$mysqli->commit();

两个查询中都没有任何错误,但是在调用commit()这些值不会存储在数据库中。 如果拆分为单独的查询并通过query()执行,则同样可以正常工作。

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

$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("s", $name);
$name = 'pratik5';
$stmt->execute();
$name = 'pratik6';
$stmt->execute();

$mysqli->commit();
$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("..."); ,即使使用简单的SELECT 1 ,它也会导致"Commands out of sync"错误;

出现此错误的原因是->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 参考: “命令不同步”

首先,在您展示的示例中,您不应该使用multi_query() 您有两个单独的语句,应该单独执行。 查看您的常识的答案

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

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

说实话,MySQL 确实在commit()上抛出错误,但 mysqli 无法将错误作为异常抛出。 我不知道是错误还是技术限制。 如果您手动检查$mysqli->error属性,您可以看到$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 最初将等待第一个生成非 DML 查询(不是 INSERT、UPDATE 或 DELETE)的结果集完成运行,然后再将控件传递回 PHP 脚本。 这允许 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;之前,需要迭代所有结果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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM