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 备注

jcn50
13 年前
注意:如果你混合使用 $mysqli->multi_query 和 $mysqli->query,后者将不会被执行!

<?php
// 错误代码:
$mysqli->multi_query(" 多条 SQL 查询语句 ; "); // 正确
$mysqli->query(" SQL 语句 #1 ; ") // 未执行!
$mysqli->query(" SQL 语句 #2 ; ") // 未执行!
$mysqli->query(" SQL 语句 #3 ; ") // 未执行!
$mysqli->query(" SQL 语句 #4 ; ") // 未执行!
?>

唯一正确的方法是

<?php
// 工作代码:
$mysqli->multi_query(" 多条 SQL 查询语句 ; "); // 正确
while ($mysqli->next_result()) {;} // 清除多条查询
$mysqli->query(" SQL 语句 #1 ; ") // 现在已执行!
$mysqli->query(" SQL 语句 #2 ; ") // 现在已执行!
$mysqli->query(" SQL 语句 #3 ; ") // 现在已执行!
$mysqli->query(" SQL 语句 #4 ; ") // 现在已执行!
?>
Ivan Gabriele
10 年前
为了能够在 MySQL > 5.3 中执行 $mysqli->query() 在 $mysqli->multi_query() 之后,我用以下代码更新了 jcn50 的代码

<?php
// 工作代码:
$mysqli->multi_query(" 多条 SQL 查询语句 ; "); // 正确

while ($mysqli->next_result()) // 清除多条查询
{
if (!
$mysqli->more_results()) break;
}

$mysqli->query(" SQL 语句 #1 ; ") // 现在已执行!
$mysqli->query(" SQL 语句 #2 ; ") // 现在已执行!
$mysqli->query(" SQL 语句 #3 ; ") // 现在已执行!
$mysqli->query(" SQL 语句 #4 ; ") // 现在已执行!
?>
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 值。
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 库并没有自动捕获这个问题。
mjmendoza at grupzero dot tk
17 年前
我正在开发自己的 CMS,我在附加数据库的 sql 文件时遇到了问题。我认为 mysqli_multi_query 有 bug,导致我的 MySQL 服务器崩溃。我试图报告这个 bug,但它显示其他开发人员已经报告了重复的 bug。令我惊讶的是,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....;...;... blah blah blah;...";

/* 执行多条语句 */
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' 语句的结果。
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));
}
Miles
15 年前
您可以对存储过程使用预处理语句。

您只需要在关闭语句之前刷新所有后续结果集... 所以

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

... 绑定,执行,绑定,获取...

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

$mysqli_stmt->close();

希望这有帮助 :o)
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;
}

?>
祝你好运!
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>ERROR</h1>
语句 #'
. ( $i + 1 ) . ' of <b>test_' . $id . '_data.sql</b>:<br /><br />
<pre>'
. $query_array[ $i ] . '</pre><br /><br />
<span style="color:red;">'
. $this->mysqli->error . '</span>'
);
}
?>
crmccar at gmail dot com
12 年前
我想强调捕获由 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() 从第二个结果开始。
keksov at gmail dot com
10 年前
如果要在一个多行查询中创建带有触发器、过程或函数的表,您可能会遇到错误 -
#1064 - 语法错误,您的 SQL 语法有误;xxx 对应于您的 MySQL 服务器版本,有关正确语法的信息,请参阅 near 'DELIMITER' at line 1

解决方案非常简单 - 完全不要使用 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
Anonymous
13 年前
如果您的第二个或后面的查询没有返回结果,或者您的查询不是有效的 SQL 查询,more_results(); 在任何情况下都会返回 true。
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://dev.mysqlserver.cn/doc/refman/5.1/en/packet-too-large.html
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 行数 == -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;
jlong at carouselchecks dot com
11 年前
在运行多语句查询后遇到 "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();
}
}

?>
Stjepan Brbot
8 年前
这个例子展示了如何从多个存储过程读取数据。这里我有两个存储过程 proc1() 和 proc2(),并将它们的数据检索到二维数组中。

<?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]; //获取第一列的值
$cols[]=$row[1]; //获取第二列的值
}
$procs[]=$cols; //将 cols 添加到 procedures 数组
}
}
while(
$this->db->more_results());
}

?>
luka8088 at owave dot net
13 年前
如果你没有遍历所有结果,就会收到 "server has gone away" 错误信息...

为了解决这个问题,在 php 5.2 中,只需要使用

<?php
// 适用于 php 5.2
while ($mysqli->next_result());
?>

来丢弃不需要的结果,但在 php 5.3 中,仅仅使用它会抛出

mysqli::next_result(): 没有下一个结果集。请调用 mysqli_more_results()/mysqli::more_results() 检查是否应该调用此函数/方法

所以它应该被替换为

<?php
// 适用于 php 5.3
while ($mysqli->more_results() && $mysqli->next_result());
?>

我还尝试过,但失败了

<?php

// 在某些情况下会创建无限循环
while ($mysqli->more_results())
$mysqli->next_result();

// 在某些情况下也会抛出错误
if ($mysqli->more_results())
while (
$mysqli->next_result());

?>
jparedes at gmail dot com
15 年前
在执行 mysqli_multi_query 后,在向服务器发送任何其他语句之前,先处理结果集非常重要,否则你的
套接字仍然被阻塞。

请注意,即使你的多语句查询不包含 SELECT 查询,服务器也会发送包含错误代码(或 OK 数据包)的结果包以表示单个语句。
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 );

?>
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 "Commands out of sync; you can't run this command now" 错误。但是,这种方法完全忽略了这样一个事实,即任何多余的结果集都可能是系统遭到入侵的迹象。

相反,明智的做法是严格控制预期的单个结果集的数量,并在收到更多结果集时抛出异常。

但是,重要的是要理解,任何关闭注释(可能作为抵御命令追加的一种防御手段而附加)都会导致一个额外的空结果集。

示例

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

将生成三个结果集

#1 - 十个数据行
#2 - 总行数
#3 - 一个空结果集,其中:FALSE === $db->use_result(),即使它之前是 TRUE === ($db->more_results() and $db->next_result() ) 。
levani0101 at yahoo dot com
10 年前
请注意,最后一个查询后面不需要分号。这浪费了我一个多小时的时间...
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