简体   繁体   English

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

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

Here is an example.这是一个例子。

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

There isn't any error in either of the queries, but when calling commit() the values are not stored in the database.两个查询中都没有任何错误,但是在调用commit()这些值不会存储在数据库中。 The same works perfectly fine if split into separate queries and executed via query() .如果拆分为单独的查询并通过query()执行,则同样可以正常工作。

You shouldn't use multi query.您不应该使用多查询。 Rewrite your code as follows重写你的代码如下

$mysqli->begin_transaction();

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

$mysqli->commit();

or, for the real-life inserts,或者,对于现实生活中的插入,

$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.

The above is identical to:以上等同于:

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

Whatever you put in $mysqli->query("...");无论你在$mysqli->query("..."); , it WILL result in a "Commands out of sync" error, even with a simple SELECT 1 ; ,即使使用简单的SELECT 1 ,它也会导致"Commands out of sync"错误;

The reason for this error is because ->commit() operation runs a single query ( commit; ).出现此错误的原因是->commit()操作运行单个查询 ( commit; )。 However, the results of the previous queries have not been read.但是,之前的查询结果还没有被读取。

When a single query() operation is used, the MySQL server will answer with a response frame that depends on the query statement.当使用单个query()操作时,MySQL 服务器将使用取决于查询语句的响应帧进行响应。

When using multi_query() , the following happens at MySQL communication protocol level:使用multi_query()时,MySQL 通信协议级别会发生以下情况:

  1. A "Request Set Option" (as displayed in Wireshark) frame is sent with "multi statements" flag set to ON .发送“请求设置选项”(如 Wireshark 中所示)帧时将“多语句”标志设置为ON
  2. A frame containing the whole string transmitted to multi_query() as request.包含作为请求传输到multi_query()的整个字符串的帧。
  3. MySQL server answers with a response that may contain different resultsets. MySQL 服务器的响应可能包含不同的结果集。 The same is true when calling a stored procedure .调用存储过程时也是如此。

Solution 1解决方案1

If you want to use multi_query() , you must have your start transaction / commit operations as part of it:如果要使用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());
*/

Solution 2解决方案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 Reference: "Commands out of sync" . MySQL 参考: “命令不同步”

First thing first, in the example you have shown you should not be using multi_query() .首先,在您展示的示例中,您不应该使用multi_query() You have two separate statements which should be executed separately.您有两个单独的语句,应该单独执行。 See Your Common Sense's answer查看您的常识的答案

multi_query() should be rarely used. multi_query()应该很少使用。 It should be used only in situations when you already have a string composed of multiple queries, which you absolutely trust.它应该仅在您已经拥有由多个查询组成的字符串且您绝对信任的情况下使用。 Don't ever allow variable input into multi_query() !永远不要允许变量输入multi_query()

Why commit() doesn't work after multi_query() ?为什么在multi_query()之后commit()不起作用?

Truth be told MySQL does throw an error on commit() , but mysqli is not able to throw the error as exception.说实话,MySQL 确实在commit()上抛出错误,但 mysqli 无法将错误作为异常抛出。 Whether it is a bug or a technical limitation I do not know.我不知道是错误还是技术限制。 You can see the error if you check manually $mysqli->error property.如果您手动检查$mysqli->error属性,您可以看到$mysqli->error You should see an error as follows:您应该会看到如下错误:

Commands out of sync;命令不同步; you can't run this command now你现在不能运行这个命令

However, the exception is thrown correctly once you call use_result() or store_result() .但是,一旦您调用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

The reason why MySQL says the commands are out of sync is because you can only execute a single command at a time per each MySQL session. MySQL 说命令不同步的原因是每个 MySQL 会话一次只能执行一个命令。 However, multi_query() sends the whole SQL string to MySQL in a single command, and let's MySQL run the queries asynchronously.但是, multi_query()在单个命令中将整个 SQL 字符串发送到 MySQL,让 MySQL 异步运行查询。 PHP will initially wait for the first result-set producing non-DML query (not INSERT, UPDATE or DELETE) to finish running before the control is passed back to PHP script. PHP 最初将等待第一个生成非 DML 查询(不是 INSERT、UPDATE 或 DELETE)的结果集完成运行,然后再将控件传递回 PHP 脚本。 This allows MySQL to run the rest of the SQL on the server and buffer the results, while you can do some other operations in PHP and collect the results later.这允许 MySQL 在服务器上运行其余的 SQL 并缓冲结果,而您可以在 PHP 中执行一些其他操作并稍后收集结果。 You can't run another SQL command on the same connection until you iterate over all the results from the previous asynchronous queries.您不能在同一个连接上运行另一个 SQL 命令,除非您遍历之前异步查询的所有结果。

As pointed out in another answer, commit() will try to execute another command on the same connection.正如另一个答案中所指出的, commit()将尝试在同一连接上执行另一个命令。 You are getting the out of sync error, because you simply haven't finished processing the multi_query() command.您收到了不同步错误,因为您还没有完成multi_query()命令的处理。

MySQLi blocking loop MySQLi 阻塞循环

Each asynchronous query should be followed by a blocking loop in mysqli.每个异步查询都应该跟在 mysqli 中的阻塞循环之后。 A blocking loop will iterate over all the executed queries on the server and fetch the results one by one, which you can then process in PHP.阻塞循环将遍历服务器上所有已执行的查询并逐个获取结果,然后您可以在 PHP 中处理这些结果。 Here is an example of such loop:这是此类循环的示例:

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

You must have the blocking loop always, even when you know the queries are not going to produce any result-sets.您必须始终使用阻塞循环,即使您知道查询不会产生任何结果集。

Solution解决方案

Stay away from multi_query() !远离multi_query() Most of the time there's a better way of executing SQL files.大多数情况下,有一种更好的方式来执行 SQL 文件。 If you have your queries separate, then don't concatenate them together, and execute each on their own.如果您的查询是分开的,则不要将它们连接在一起,而是单独执行每个查询。

If you really need to use multi_query() , and you would like to wrap it in transaction, you must put the commit() after the blocking loop.如果您确实需要使用multi_query() ,并且想将其包装在事务中,则必须将commit()放在阻塞循环之后。 All the results need to be iterated over before you can execute the COMMIT;在执行COMMIT;之前,需要迭代所有结果COMMIT; command.命令。

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

Of course to see any of the mysqli errors you need to enable exception mode.当然,要查看您需要启用异常模式的任何 mysqli 错误。 Simply, put this line before new 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