【问题标题】:Why does multi_query not work when I use transactions?为什么我使用事务时 multi_query 不起作用?
【发布时间】:2017-02-26 12:51:33
【问题描述】:

这是一个例子。

$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() 执行,同样的工作非常好。

【问题讨论】:

  • 除了已经给出的答案,我不会为此使用事务。请记住,事务块可能会对其他同时连接的性能产生不良影响。为什么不直接使用insert into test (Name) values ('pratik5'), ('pratik6');

标签: php mysqli mysqli-multi-query multi-query


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

$mysqli->commit();

【讨论】:

  • 我可以知道原因吗?
  • 是否有任何文档指定 multi_query 在使用事务时存在一些问题。据我所知,你给出的解决方案。但是使用你的解决方案意味着我的应用程序服务器在我可以为一组查询累积发送数据时,向我的数据库服务器发送数据。
  • @PratikSolanki 我还没有完成文档。只是把它作为一个经验法则。此外,对于 real 插入,无论如何您都必须使用 prepare()/execute(),这会完全结束参数
  • @YourCommonSense: multi_query()不是异步的,这不是失败的原因,这是因为所有结果集都应该被消耗/释放。
【解决方案2】:
$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".

【讨论】:

  • 可能是因为您缺少测试$test 应该是false,并且可以使用echo $ysqli->error; 访问错误
  • MySQL 允许多次查询的通信协议不知道内容。 mysqli->multi_query() 也是如此。无论SELECTUPDATE/DELETE/INSERT 的类型如何,服务器都会为每个单独的查询生成一个准确的结果集。这意味着如果它们是 $sql1 中的 2 个语句,则将进入两次 while 循环,在其中放入一些 echo 语句,您将看到它在运行。
  • INSERT 不会产生结果,除非将它们放入对 multi_query() 的调用中。当使用multi_query() 时,不是PHP 将提供的字符串中的各种查询分开并分别发送它们。它是底层 MySQL 客户端库,它发送带有“多语句标志”集的“请求集选项”,以便下一个 MySQL 请求包含传递给multi_query() 的整个字符串。 PHP 不分析传递给multi_query() 的内容,它按原样发送整个内容。
  • 这都是很好的信息。您应该将其纳入您的答案中。
  • @YourCommonSense 我想说,如果可能的话,multi_query() 根本不应该使用,但是,如果目标是运行外部生成的脚本,那么它可能是唯一足够好的自定义解决方案在 PHP 中解析可能会导致重新发明方轮。现在,在解决方案 1 和 2 之间,我个人会推荐第一个。结合单个查询(这是 begin_transaction()/commit() 在后台使用的)和 multi_queries() 听起来非常震撼。因此,为什么解决方案 2 使用看起来有点 hacky 的东西来释放结果集。
【解决方案3】:

首先,在您展示的示例中,您不应该使用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 最初会等待第一个生成非 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; 命令之前,需要对所有结果进行迭代。

$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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 2022-11-03
    • 2021-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多