PHP Conference Japan 2024

mysqli::multi_query

mysqli_multi_query

(PHP 5, PHP 7, PHP 8)

mysqli::multi_query -- mysqli_multi_query在資料庫上執行一個或多個查詢

描述

物件導向風格

public mysqli::multi_query(string $query): bool

程序式風格

mysqli_multi_query(mysqli $mysql, string $query): bool

執行一個或多個以分號串接的查詢。

警告

安全性警告:SQL 注入

如果查詢包含任何變數輸入,則應改用參數化預備陳述式。 或者,必須正確格式化資料,並且必須使用 mysqli_real_escape_string() 函式來逸脫所有字串。

查詢在單次呼叫中非同步地傳送到資料庫,但資料庫會依序處理它們。mysqli_multi_query() 會等待第一個查詢完成,然後再將控制權返回給 PHP。 然後,MySQL 伺服器將處理序列中的下一個查詢。 一旦下一個結果準備就緒,MySQL 將等待 PHP 下一次執行 mysqli_next_result()

建議使用do-while來處理多個查詢。 連線將會忙碌,直到所有查詢完成並將結果擷取到 PHP 為止。 在處理完所有查詢之前,無法在同一個連線上發出其他陳述式。 要繼續處理序列中的下一個查詢,請使用 mysqli_next_result()。 如果下一個結果尚未準備好,mysqli 將會等待 MySQL 伺服器的回應。 要檢查是否有更多結果,請使用 mysqli_more_results()

對於產生結果集的查詢,例如 SELECT, SHOW, DESCRIBEEXPLAIN,可以使用 mysqli_use_result()mysqli_store_result() 來擷取結果集。 對於不產生結果集的查詢,可以使用相同的函式來擷取資訊,例如受影響的列數。

提示

為預存程序執行 CALL 陳述式可能會產生多個結果集。 如果預存程序包含 SELECT 陳述式,則會按照程序執行時產生的順序返回結果集。 一般來說,呼叫者無法得知程序將返回多少個結果集,並且必須準備好擷取多個結果。 程序的最終結果是不包含結果集的狀態結果。 該狀態表示程序是否成功或發生錯誤。

參數

mysql

僅限程序式風格:由 mysqli_connect()mysqli_init() 返回的 mysqli 物件。

query

包含要執行的查詢的字串。 多個查詢必須以分號分隔。

傳回值

如果第一個陳述式失敗,則返回 false。 要從其他陳述式擷取後續錯誤,您必須先呼叫 mysqli_next_result()

錯誤/例外

如果已啟用 mysqli 錯誤報告 (MYSQLI_REPORT_ERROR) 且要求的操作失敗,則會產生警告。 如果另外將模式設定為 MYSQLI_REPORT_STRICT,則會改為擲回 mysqli_sql_exception

範例

範例 1 mysqli::multi_query() 範例

物件導向風格

<?php

mysqli_report
(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");

$query = "SELECT CURRENT_USER();";
$query .= "SELECT Name FROM City ORDER BY ID LIMIT 20, 5";

/* 執行多重查詢 */
$mysqli->multi_query($query);
do {
/* 將結果集儲存在 PHP 中 */
if ($result = $mysqli->store_result()) {
while (
$row = $result->fetch_row()) {
printf("%s\n", $row[0]);
}
}
/* 列印分隔符號 */
if ($mysqli->more_results()) {
printf("-----------------\n");
}
} while (
$mysqli->next_result());

程序式風格

<?php

mysqli_report
(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$link = mysqli_connect("localhost", "my_user", "my_password", "world");

$query = "SELECT CURRENT_USER();";
$query .= "SELECT Name FROM City ORDER BY ID LIMIT 20, 5";

/* 執行多重查詢 */
mysqli_multi_query($link, $query);
do {
/* 將結果集儲存在 PHP 中 */
if ($result = mysqli_store_result($link)) {
while (
$row = mysqli_fetch_row($result)) {
printf("%s\n", $row[0]);
}
}
/* 印出分隔線 */
if (mysqli_more_results($link)) {
printf("-----------------\n");
}
} while (
mysqli_next_result($link));

上面的範例會輸出類似以下的內容

my_user@localhost
-----------------
Amersfoort
Maastricht
Dordrecht
Leiden
Haarlemmermeer

另請參閱

新增註解

使用者貢獻的註解 22 則註解

154
jcn50
13 年前
注意:如果你混合使用 $mysqli->multi_query 和 $mysqli->query,後者將不會被執行!

<?php
// 不良程式碼:
$mysqli->multi_query(" 許多 SQL 查詢 ; "); // OK
$mysqli->query(" SQL 陳述式 #1 ; ") // 不會執行!
$mysqli->query(" SQL 陳述式 #2 ; ") // 不會執行!
$mysqli->query(" SQL 陳述式 #3 ; ") // 不會執行!
$mysqli->query(" SQL 陳述式 #4 ; ") // 不會執行!
?>

唯一正確的方法是

<?php
// 可行的程式碼:
$mysqli->multi_query(" 許多 SQL 查詢 ; "); // OK
while ($mysqli->next_result()) {;} // 清空 multi_queries
$mysqli->query(" SQL 陳述式 #1 ; ") // 現在執行!
$mysqli->query(" SQL 陳述式 #2 ; ") // 現在執行!
$mysqli->query(" SQL 陳述式 #3 ; ") // 現在執行!
$mysqli->query(" SQL 陳述式 #4 ; ") // 現在執行!
?>
20
Ivan Gabriele
10 年前
為了能在 MySQL > 5.3 的情況下,在 $mysqli->multi_query() 之後執行 $mysqli->query(),我用以下程式碼更新了 jcn50 的程式碼

<?php
// 可行的程式碼:
$mysqli->multi_query(" 許多 SQL 查詢 ; "); // OK

while ($mysqli->next_result()) // 清空 multi_queries
{
if (!
$mysqli->more_results()) break;
}

$mysqli->query(" SQL 陳述式 #1 ; ") // 現在執行!
$mysqli->query(" SQL 陳述式 #2 ; ") // 現在執行!
$mysqli->query(" SQL 陳述式 #3 ; ") // 現在執行!
$mysqli->query(" SQL 陳述式 #4 ; ") // 現在執行!
?>
9
miqrogroove at gmail dot com
11 年前
以下是關於 multi_query() 的錯誤檢查和傳回值的更多詳細資訊。測試顯示,每個結果都有一些 mysqli 屬性需要檢查

affected_rows
errno
error
insert_id
warning_count

如果 error 或 errno 不為空,則剩餘的查詢不會傳回任何內容,即使繼續處理後續結果,error 和 errno 也會顯示為空。

另請注意,get_warnings() 無法與 multi_query() 搭配使用。它只能在遍歷所有結果後使用,並且它只會取得最後一個查詢的警告,而不會取得其他查詢的警告。如果你需要查看或記錄查詢警告字串,則必須不要使用 multi_query(),因為你只能看到 warning_count 值。
4
info at ff dot net
18 年前
請注意,你需要使用此函數來呼叫預存程序!

如果你的預存程序呼叫遇到「與 MySQL 伺服器的連線遺失」錯誤,則表示你沒有提取 'OK'(或 'ERR')訊息,這是預存程序呼叫的第二個結果集。你必須提取該結果,後續查詢才不會出現問題。

不良範例,後續呼叫時會不時失敗
<?php
$sQuery
='CALL exampleSP('param')';
if(!
mysqli_multi_query($this->sqlLink,$sQuery))
$this->queryError();
$this->sqlResult=mysqli_store_result($this->sqlLink);
?>

可行的範例
<?php
$sQuery
='CALL exampleSP('param')';
if(!
mysqli_multi_query($this->sqlLink,$sQuery))
$this->queryError();
$this->sqlResult=mysqli_store_result($this->sqlLink);

if(
mysqli_more_results($this->sqlLink))
while(
mysqli_next_result($this->sqlLink));
?>

當然,你可以對多個結果執行更多操作,而不僅僅是將它們丟棄,但對於大多數情況來說,這就足夠了。例如,你可以建立一個 "sp" 函數,該函數將會清除第二個 'ok' 結果。

這個惱人的 'OK' 訊息害我花了數小時試圖找出為什麼 MySQL 伺服器會記錄「來自客戶端的錯誤封包」警告,以及 PHP 的 mysql_error() 會顯示「連線遺失」。mysqli 函式庫竟然沒有自動處理這個問題,實在令人遺憾。
11
mjmendoza at grupzero dot tk
18 年前
我當時在開發自己的 CMS,而且在附加資料庫的 SQL 檔案時遇到問題。我以為 mysqli_multi_query 有錯誤導致我的 MySQL 伺服器崩潰。我試著回報錯誤,但顯示其他開發人員已經有重複的錯誤回報。令我驚訝的是,即使沒有結果,mysqli_multi_query 也需要處理結果。

最後,當我複製範例並刪除一些東西後,它才開始運作。以下是它看起來的樣子:

<?php
$link
= mysqli_connect("localhost", "my_user", "my_password", "world");

/* 檢查連線 */
if (mysqli_connect_errno()) {
printf("連線失敗: %s\n", mysqli_connect_error());
exit();
}

$query = "CREATE TABLE....;...;... 一堆有的沒的;...";

/* 執行多重查詢 */
if (mysqli_multi_query($link, $query)) {
do {
/* 儲存第一個結果集 */
if ($result = mysqli_store_result($link)) {
//因為沒有東西要處理,所以不做任何事
mysqli_free_result($result);
}
/* 列印分隔符號 */
if (mysqli_more_results($link)) {
//我保留這個,因為它似乎有用
//試著移除它,自己看看會發生什麼
}
} while (
mysqli_next_result($link));
}

/* 關閉連線 */
mysqli_close($link);
?>

重點是:我認為 mysql_multi_query 應該只用於附加資料庫。很難在單一的 while 迴圈中處理 'SELECT' 陳述式的結果。
2
vicky dot gonsalves at outlook dot com
10 年前
可以使用以下程式碼來解決
mysqli::next_result():沒有下一個結果集。請呼叫 mysqli_more_results()/mysqli::more_results() 來檢查是否需要呼叫這個函式/方法

$query = "SELECT SOME_COLUMN FROM SOME_TABLE_NAME;";
$query .= "SELECT SOME_OTHER_COLUMN FROM SOME_TABLE_NAME";
/* 執行多重查詢 */
if (mysqli_multi_query($this->conn, $query)) {
$i = true;
do {
/* 儲存第一個結果集 */
if ($result = mysqli_store_result($this->conn)) {
while ($row = mysqli_fetch_row($result)) {
printf("%s\n", $row[0]);
}
mysqli_free_result($result);
}
/* 列印分隔符號 */
if (mysqli_more_results($this->conn)) {
$i = true;
printf("-----------------\n");
} else {
$i = false;
}
} while ($i && mysqli_next_result($this->conn));
}
1
Miles
15 年前
你可以在預存程序中使用預備陳述式。

你只需要在關閉陳述式之前清除所有後續的結果集...所以

$mysqli_stmt = $mysqli->prepare(....);

... bind, execute, bind, fetch ...

while($mysqli->more_results())
{
$mysqli->next_result();
$discard = $mysqli->store_result();
}

$mysqli_stmt->close();

希望這有幫助 :o)
4
Lubaev K
11 年前
使用產生器。
PHP 5.5.0
<?php
// 快速多重查詢函式。
function multiQuery( mysqli $mysqli, $query ) {
if (
$mysqli->multi_query( $query )) {
do {
if (
$result = $mysqli->store_result()) {
while (
$row = $result->fetch_row()) {
foreach (
$row as $key => $value) yield $key => $value;
}
$result->free();
}
} while(
$mysqli->more_results() && $mysqli->next_result() );
}
}

$query = "OPTIMIZE TABLE `question`;" .
"LOCK TABLES `question` READ;" .
"SELECT * FROM `question` WHERE `questionid`=2;" .
"SELECT * FROM `question` WHERE `questionid`=7;" .
"SELECT * FROM `question` WHERE `questionid`=8;" .
"SELECT * FROM `question` WHERE `questionid`=9;" .
"SELECT * FROM `question` WHERE `questionid`=11;" .
"SELECT * FROM `question` WHERE `questionid`=12;" .
"UNLOCK TABLES;" .
"TRUNCATE TABLE `question`;";

$mysqli = new mysqli('localhost', 'user', 'pswd', 'dbnm');
$mysqli->set_charset("cp1251");

// 結果
foreach ( multiQuery($mysqli, $query) as $key => $value ) {
echo
$key, $value, PHP_EOL;
}

?>
祝你好運!
4
skunkbad
10 年前
我很感謝 crmccar at gmail dot com 關於如何正確檢查錯誤的建議,但我用他/她的程式碼會出現錯誤。我稍微修改了一下程式碼來修正它

<?php
$sql
= file_get_contents( 'sql/test_' . $id . '_data.sql');

$query_array = explode(';', $sql);

// 執行 SQL
$i = 0;
if(
$this->mysqli->multi_query( $sql ) )
{
do {
$this->mysqli->next_result();

$i++;
}
while(
$this->mysqli->more_results() );
}

if(
$this->mysqli->errno )
{
die(
'<h1>錯誤</h1>
查詢 #'
. ( $i + 1 ) . ' 在 <b>test_' . $id . '_data.sql</b>:<br /><br />
<pre>'
. $query_array[ $i ] . '</pre><br /><br />
<span style="color:red;">'
. $this->mysqli->error . '</span>'
);
}
?>
6
crmccar at gmail dot com
13 年前
我想強調正確捕捉 multi_query() 執行查詢的錯誤的方式,因為手冊中的範例沒有顯示,而且很容易在不知道的情況下遺失 UPDATE、INSERT 等操作。

如果 $mysqli->next_result() 沒有更多陳述式要執行,或是下一個陳述式有錯誤,它將會回傳 false。因此,在迴圈結束時檢查錯誤非常重要。此外,我認為知道迴圈中斷的時間和位置很有用,因此請考慮以下程式碼:

<?php
$statements
= array("INSERT INTO tablename VALUES ('1', 'one')", "INSERT INTO tablename VALUES ('2', 'two')");
if (
$mysqli->multi_query(implode(';', $statements))) {
$i = 0;
do {
$i++;
} while (
$mysqli->next_result());
}
if (
$mysqli->errno) {
echo
"批次執行在陳述式 $i提前結束。\n";
var_dump($statements[$i], $mysqli->error);
}
?>

在 multi_query() 呼叫中的 IF 語句檢查第一個結果,因為 next_result() 從第二個結果開始。
2
keksov at gmail dot com
11 年前
如果您想在一個多行查詢中建立包含觸發器、程序或函數的表格,您可能會遇到錯誤 -
#1064 - 您的 SQL 語法中有錯誤;xxx 對應您的 MySQL 伺服器版本,以便在第 1 行的 'DELIMITER' 附近使用正確的語法

解決方案非常簡單 - 完全不要使用 DELIMITER 關鍵字!因此,請不要使用

DELIMITER |
CREATE TRIGGER $dbName.$iname BEFORE INSERT ON $table FOR EACH ROW
BEGIN
<body>
EOT|
DELIMITER ;

改為使用

CREATE TRIGGER $dbName.$iname BEFORE INSERT ON $table FOR EACH ROW
BEGIN
<body>
EOT;

如需更多資訊,請閱讀 StackOverflow 上關於問題 #5311141 的解答

http://stackoverflow.com/questions/5311141/how-to-execute-mysql-command-delimiter
1
Anonymous
13 年前
如果您的第二個或後續查詢沒有傳回結果,甚至如果您的查詢不是有效的 SQL 查詢,more_results(); 在任何情況下都會傳回 true。
1
Shawn Pyle
14 年前
請務必不要傳送大於您的 MySQL 伺服器上 max_allowed_packet 大小的查詢集。如果您這樣做,您會收到類似如下的錯誤:
Mysql Error (1153): Got a packet bigger than 'max_allowed_packet' bytes

要查看您的 MySQL 大小限制,請執行以下查詢:show variables like 'max_allowed_packet';

或參閱 https://mysqldev.dev.org.tw/doc/refman/5.1/en/packet-too-large.html
-1
ashteroid
4 年前
要從所有查詢取得受影響/選取的列數

$q = "UPDATE `Review` SET `order` = 1 WHERE id = 600;" // aff 1
. "UPDATE `Review` SET `order` = 600 WHERE id = 1;" //aff 1
. "SELECT 0;" //用於測試,aff rows == -1
;

$affcnt = 0;
$rowcnt = 0;

$res = $db->multi_query($q);
if($res == false)
Lib::throw( $q . "\n[" . $db->errno . "]\n" . $db->error . "\n" );
do
{
$affcnt += $db->affected_rows;
if( isset($res->num_rows) )
$rowcnt += $res->num_rows;
}
while( $db->more_results() && $res = $db->next_result() );
//重要:先呼叫 more_results!,然後呼叫 next_result 以取得新資料。

return $res;
1
jlong at carouselchecks dot com
12 年前
在執行多重查詢後取得「Error: Commands out of sync; you can't run this command now」錯誤訊息?請確保您已清除結果佇列。

以下是我用來捨棄多重查詢中所有後續結果的方法

<?php
while($dbLink->more_results() && $dbLink->next_result()) {
$extraResult = $dbLink->use_result();
if(
$extraResult instanceof mysqli_result){
$extraResult->free();
}
}

?>
0
Stjepan Brbot
8 年前
此範例示範如何從多個預存程序讀取資料。在這裡,我有兩個預存程序 proc1() 和 proc2(),並將它們的資料擷取到 2D 陣列中

<?php

$db
=new mysqli(...);

$sql="CALL proc1(...); CALL proc2(...);";

$procs=[]; //結果集(表格)的外部陣列
$cols=[]; //欄(欄位)的內部陣列

if($db->multi_query($sql))
{
do
{
$db->next_result();
if(
$rst=$db->use_result())
{
while(
$row=$rst->fetch_row())
{
$cols[]=$row[0]; //擷取第 1 個欄位值
$cols[]=$row[1]; //擷取第 2 個欄位值
}
$procs[]=$cols; //將欄位新增至程序陣列
}
}
while(
$this->db->more_results());
}

?>
0
luka8088 at owave dot net
14 年前
如果您不迭代所有結果,您會收到「server has gone away」錯誤訊息...

要解決此問題,在 php 5.2 中,使用以下程式碼就足夠了

<?php
// ok for php 5.2
while ($mysqli->next_result());
?>

以捨棄不想要的結果,但在 php 5.3 中,僅使用此程式碼會拋出

mysqli::next_result():沒有下一個結果集。請呼叫 mysqli_more_results()/mysqli::more_results() 來檢查是否需要呼叫這個函式/方法

因此,應將其取代為

<?php
// ok for php 5.3
while ($mysqli->more_results() && $mysqli->next_result());
?>

我也嘗試過但失敗了

<?php

// can create infinite look in some cases
while ($mysqli->more_results())
$mysqli->next_result();

// also throws error in some cases
if ($mysqli->more_results())
while (
$mysqli->next_result());

?>
1
jparedes at gmail dot com
16 年前
非常重要的是,在執行 mysqli_multi_query 後,您必須先處理結果集,然後再向伺服器傳送任何其他陳述式,否則您的
Socket 仍然會被封鎖。

請注意,即使您的多重陳述式不包含 SELECT 查詢,伺服器也會傳送包含單一陳述式錯誤碼(或 OK 封包)的結果封包。
-1
undefined(AT)users(DOT)berlios(DOT)de
16 年前
mysqli_multi_query 處理 InnoDB 上的 MySQL 交易 :-)

<?php

$mysqli
= mysqli_connect( "localhost", "owner", "pass", "db", 3306, "/var/lib/mysql/mysql.sock" );

$QUERY = <<<EOT
START TRANSACTION;
SELECT @lng:=IF( STRCMP(`main_lang`,'de'), 'en', 'de' )
FROM `main_data` WHERE ( `main_activ` LIKE 1 ) ORDER BY `main_id` ASC;
SELECT `main_id`, `main_type`, `main_title`, `main_body`, `main_modified`, `main_posted`
FROM `main_data`
WHERE ( `main_type` RLIKE "news|about" AND `main_lang` LIKE @lng AND `main_activ` LIKE 1 )
ORDER BY `main_type` ASC;
COMMIT;
EOT;

$query = mysqli_multi_query( $mysqli, $QUERY ) or die( mysqli_error( $mysqli ) );

if(
$query )
{
do {
if(
$result = mysqli_store_result( $mysqli ) )
{
$subresult = mysqli_fetch_assoc( $result );
if( ! isset(
$subresult['main_id'] ) )
continue;

foreach(
$subresult AS $k => $v )
{
var_dump( $k , $v );
}
}
} while (
mysqli_next_result( $mysqli ) );
}

mysqli_close( $mysqli );

?>
-1
ASchmidt at Anamera dot net
6 年前
多重查詢會開啟 SQL 注入的可能性。

經常引用的「回退」迴圈

<?php
while ( $db->more_results() and $db->next_result() ) {
$rs = $db->use_result();
if(
$rs instanceof \mysqli_result ) {
$rs->free();
}
?>

這樣做當然可以避免可怕的 2014 錯誤「命令不同步;您現在無法執行此命令」。然而,這種技術將完全忽略一個事實,即任何多餘的結果集都可能是系統被入侵的跡象。

相反地,謹慎地管理預期的單獨結果集的正確數量,並在收到更多結果集時拋出例外可能才是明智之舉。

然而,重要的是要理解,任何結尾的註解(可能作為防止命令附加的一種防禦措施而附加)都會產生一個額外的、空的結果集。

範例

SELECT SQL_CALC_FOUND_ROWS * FROM `table` LIMIT 10; SELECT FOUND_ROWS(); --

將會產生三個結果集

#1 - 十個資料列,
#2 - 整體列數,
#3 - 一個空的結果集,其中:FALSE === $db->use_result(),即使之前 TRUE === ($db->more_results() 和 $db->next_result() )。
-5
levani0101 at yahoo dot com
10 年前
請注意,最後一個查詢之後不需要分號。這浪費了我一個多小時的時間...
-4
jesper at hermandsen dot dk
9 年前
如果您要導入一個包含觸發器、函數、預存程序和其他內容的 SQL 檔案,您可能會在 MySQL 中使用 DELIMITER。
注意:此函式假設所有分隔符號都位於自己的行上,並且 "DELIMITER" 為全部大寫。

<?php
function mysqli_multi_query_file($mysqli, $filename) {
$sql = file_get_contents($filename);
// 移除註解
$sql = preg_replace('#/\*.*?\*/#s', '', $sql);
$sql = preg_replace('/^-- .*[\r\n]*/m', '', $sql);
if (
preg_match_all('/^DELIMITER\s+(\S+)$/m', $sql, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
$prev = null;
$index = 0;
foreach (
$matches as $match) {
$sqlPart = substr($sql, $index, $match[0][1] - $index);
// 將游標移到分隔符號之後
$index = $match[0][1] + strlen($match[0][0]);
if (
$prev && $prev[1][0] != ';') {
$sqlPart = explode($prev[1][0], $sqlPart);
foreach (
$sqlPart as $part) {
if (
trim($part)) { // 沒有空查詢
$mysqli->query($part);
}
}
} else {
if (
trim($sqlPart)) { // 沒有空查詢
$mysqli->multi_query($sqlPart);
while (
$mysqli->next_result()) {;}
}
}
$prev = $match;
}
// 執行最後一個分隔符號之後的 SQL
$sqlPart = substr($sql, $index, strlen($sql)-$index);
if (
$prev && $prev[1][0] != ';') {
$sqlPart = explode($prev[1][0], $sqlPart);
foreach (
$sqlPart as $part) {
if (
trim($part)) {
$mysqli->query($part);
}
}
} else {
if (
trim($sqlPart)) {
$mysqli->multi_query($sqlPart);
while (
$mysqli->next_result()) {;}
}
}
} else {
$mysqli->multi_query($sql);
while (
$mysqli->next_result()) {;}
}
}
?>
To Top