簡體   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