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(字符串 $query): 布尔值

过程化风格

mysqli_multi_query(mysqli $mysql, 字符串 $query): 布尔值

执行一个或多个由分号连接的查询。

警告

安全警告: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()) {;} // 清空 multi_queries
$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()) // 清空 multi_queries
{
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
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....;...;... 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
// 快速的 multiQuery 函数。
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>错误</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
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()` 从第二个开始。
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
Anonymous
13 年前
如果你的第二个或后面的查询没有返回结果,或者即使你的查询不是有效的 SQL 查询,`more_results()` 在任何情况下都会返回 true。
Shawn Pyle
14 年前
确保不要发送一组大于 MySQL 服务器上 `max_allowed_packet` 大小的查询。如果你这样做,你会收到类似的错误
Mysql 错误 (1153):获取到的数据包大小超过了 'max_allowed_packet' 字节

要查看你的 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;" // 受影响 1 行
. "UPDATE `Review` SET `order` = 600 WHERE id = 1;" // 受影响 1 行
. "SELECT 0;" // 用于测试,受影响行数 == -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
12 年前
在运行多查询后收到“错误:命令不同步;你现在无法运行此命令”吗?确保你已清空结果队列。

以下是我用来丢弃多查询的所有后续结果的方法

<?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
14 年前
如果你没有遍历所有结果,你会收到“服务器已断开连接”的错误消息…

要解决此问题,在 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
16 年前
非常重要的是,在执行 `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“命令不同步;你现在无法运行此命令”错误。但是,该技术将完全忽略这样一个事实,即任何多余的结果集都可能是系统遭到入侵的迹象。

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

但是,重要的是要理解,任何结束注释(可能已被附加作为一种防止命令附加的防御措施)都会导致一个额外的空结果集。

示例

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



将产生三个结果集

第一个 - 十行数据,
第二个 - 总行数,
第三个 - 一个空的结果集,其中: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