PHP Conference Japan 2024

mysqli_stmt::prepare

mysqli_stmt_prepare

(PHP 5, PHP 7, PHP 8)

mysqli_stmt::prepare -- mysqli_stmt_prepare准备SQL语句以供执行

描述

面向对象风格

public mysqli_stmt::prepare(string $query): bool

过程化风格

mysqli_stmt_prepare(mysqli_stmt $statement, string $query): bool

准备一条语句以供执行。查询必须包含单个SQL语句。

语句模板可以包含零个或多个问号(?)参数标记——也称为占位符。在执行语句之前,必须使用mysqli_stmt_bind_param()将参数标记绑定到应用程序变量。

注意:

如果传递给mysqli_stmt_prepare()的语句长度超过服务器的max_allowed_packet,则返回的错误代码取决于您使用的是MySQL原生驱动程序(mysqlnd)还是MySQL客户端库(libmysqlclient)。行为如下:

  • mysqlnd在Linux上返回错误代码1153。错误消息意味着“收到的数据包大于max_allowed_packet字节”。

  • mysqlnd在Windows上返回错误代码2006。此错误消息意味着“服务器已断开”。

  • libmysqlclient在所有平台上都返回错误代码2006。此错误消息意味着“服务器已断开”。

参数

statement

仅过程化风格:由mysqli_stmt_init()返回的mysqli_stmt对象。

query

查询,作为字符串。它必须包含单个SQL语句。

SQL语句可以包含零个或多个由问号(?)字符表示的参数标记,位于适当的位置。

注意:

这些标记仅在SQL语句的某些位置才合法。例如,它们允许在INSERT语句的VALUES()列表中(用于指定行的列值),或在与WHERE子句中的列进行比较时(用于指定比较值)。但是,它们不允许用于标识符(例如表名或列名)。

返回值

成功时返回true,失败时返回false

错误/异常

如果启用了mysqli错误报告(MYSQLI_REPORT_ERROR)并且请求的操作失败,则会生成警告。如果此外模式设置为MYSQLI_REPORT_STRICT,则会抛出mysqli_sql_exception

示例

示例 #1 mysqli_stmt::prepare() 示例

面向对象风格

<?php

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

$city = "Amersfoort";

/* 创建预处理语句 */
$stmt = $mysqli->stmt_init();
$stmt->prepare("SELECT District FROM City WHERE Name=?");

/* 绑定参数标记 */
$stmt->bind_param("s", $city);

/* 执行查询 */
$stmt->execute();

/* 绑定结果变量 */
$stmt->bind_result($district);

/* 获取值 */
$stmt->fetch();

printf("%s is in district %s\n", $city, $district);

过程化风格

<?php

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

$city = "Amersfoort";

/* 创建预处理语句 */
$stmt = mysqli_stmt_init($link);
mysqli_stmt_prepare($stmt, "SELECT District FROM City WHERE Name=?");

/* 绑定参数标记 */
mysqli_stmt_bind_param($stmt, "s", $city);

/* 执行查询 */
mysqli_stmt_execute($stmt);

/* 绑定结果变量 */
mysqli_stmt_bind_result($stmt, $district);

/* 获取值 */
mysqli_stmt_fetch($stmt);

printf("%s is in district %s\n", $city, $district);

以上示例将输出

Amersfoort is in district Utrecht

参见

添加笔记

用户贡献笔记 9条笔记

logos-php at kith dot org
12年前
请注意,如果使用问号作为字符串值的占位符,则不要在 MySQL 查询中用引号将其括起来。

例如,请执行以下操作

mysqli_stmt_prepare($stmt, "SELECT * FROM foo WHERE foo.Date > ?");

请勿执行以下操作

mysqli_stmt_prepare($stmt, "SELECT * FROM foo WHERE foo.Date > '?'");

如果在查询中用引号将问号括起来,则 PHP 不会将问号识别为占位符,然后当尝试使用 mysqli_stmt_bind_param() 时,它会给出参数数量错误的提示。

此页面上的官方示例中隐式地说明了不使用字符串占位符引号的情况,但在文档中没有明确说明,我很难弄清楚,因此我认为值得发布。
logos-php at kith dot orgpp
12年前
事实证明,不能直接对在 IN() 子句中包含占位符的查询使用预处理语句。

有一些解决方法(例如,构造一个由 n 个逗号分隔的问号组成的字符串,然后在 IN() 子句中使用该占位符集),但不能只说 IN (?)。

这是 MySQL 的限制,而不是 PHP 的限制,但在 MySQL 文档中也没有真正记录,因此我认为值得在这里提及。

(顺便说一句,事实证明其他人之前已经发布了我上一条评论中提到的信息,关于不使用引号的信息。对于重复内容,我表示歉意;我不确定我如何错过了之前的评论。)
andrey at php dot net
19年前
如果选择 LOB,请使用以下执行顺序,否则可能会导致 mysqli 分配超出实际使用的内存

1) prepare()
2) execute()
3) store_result()
4) bind_result()

如果跳过步骤 3) 或交换步骤 3) 和 4),则 mysqli 将为该列的最大长度分配内存,对于 tinyblob 为 255,对于 blob 为 64k(仍然可以),对于 MEDIUMBLOB 为 16MB - 相当多,对于 LONGBLOB 为 4G(如果内存足够大则很好)。当存在 LOB 时,使用此顺序的查询速度稍慢一些,但这是避免内存耗尽的代价。
kontakt at arthur minus schiwon dot de
16年前
如果用引号括起占位符,您将遇到类似“准备好的语句中的变量数量与参数数量不匹配”的警告(至少在 INSERT 语句中是这样)。
ndungi at gmail dot com
15年前
`prepare`、`bind_param`、`bind_result`、`fetch` 结果、`close` 语句循环有时可能很繁琐。这是一个对象,当您只需要一个 select 时,它可以为您完成所有 mysqli 的繁琐工作,让您只需在准备好的语句上使用基本的 `preparedSelect`。该方法将结果集作为二维关联数组返回,其中 `select`ed 列作为键。

我使用了来自 http://www.biblesql.net/sites/biblesql.net/files/bible.mysql.gz. 的 bible.sql 数据库。

巴拉卡电报!

============================

<?php

class DB
{
public
$connection;

#建立数据库连接
public function __construct($host="localhost", $user="user", $pass="", $db="bible")
{
$this->connection = new mysqli($host, $user, $pass, $db);

if(
mysqli_connect_errno())
{
echo(
"数据库连接错误:"
. mysqli_connect_error($mysqli));
}
}

#存储 mysqli 对象
public function connect()
{
return
$this->connection;
}

#运行预处理查询
public function runPreparedQuery($query, $params_r)
{
$stmt = $this->connection->prepare($query);
$this->bindParameters($stmt, $params_r);

if (
$stmt->execute()) {
return
$stmt;
} else {
echo(
"语句 $statement 中出现错误:"
. mysqli_error($this->connection));
return
0;
}

}

#使用绑定参数和绑定结果运行 select 语句。
#返回一个易于使用数组函数操作的二维关联数组。

public function preparedSelect($query, $bind_params_r)
{
$select = $this->runPreparedQuery($query, $bind_params_r);
$fields_r = $this->fetchFields($select);

foreach (
$fields_r as $field) {
$bind_result_r[] = &${$field};
}

$this->bindResult($select, $bind_result_r);

$result_r = array();
$i = 0;
while (
$select->fetch()) {
foreach (
$fields_r as $field) {
$result_r[$i][$field] = $$field;
}
$i++;
}
$select->close();
return
$result_r;
}


#接收绑定参数数组并将它们绑定到执行的预处理语句的结果

private function bindParameters(&$obj, &$bind_params_r)
{
call_user_func_array(array($obj, "bind_param"), $bind_params_r);
}

private function
bindResult(&$obj, &$bind_result_r)
{
call_user_func_array(array($obj, "bind_result"), $bind_result_r);
}

#返回所选字段名称列表

private function fetchFields($selectStmt)
{
$metadata = $selectStmt->result_metadata();
$fields_r = array();
while (
$field = $metadata->fetch_field()) {
$fields_r[] = $field->name;
}

return
$fields_r;
}
}
#类结束

#DB类的使用示例

$DB = new DB("localhost", "root", "", "bible");
$var = 5;
$query = "SELECT abbr, name from books where id > ?" ;
$bound_params_r = array("i", $var);

$result_r = $DB->preparedSelect($query, $bound_params_r);

#循环遍历结果数组并显示结果

foreach ($result_r as $result) {
echo
$result['abbr'] . " : " . $result['name'] . "<br/>" ;
}

?>
mhradek AT gmail.com
16年前
此函数和 call_user_func_array 函数的一个特别有用的改编

// $params 作为数组发送 ($val=>'i', $val=>'d' 等...)

function db_stmt_bind_params($stmt, $params)
{
$funcArg[] = $stmt;
foreach($params as $val=>$type)
{
$funcArg['type'] .= $type;
$funcArg[] = $val;
}
return call_user_func_array('mysqli_stmt_bind_param', $funcArgs);
}

感谢 sned 提供的代码。
st dot john dot johnson at gmail dot com
17 年前
关于 lachlan76 之前所说,只要在再次执行之前告诉数据库切换到下一个结果集,存储过程就可以通过预处理语句执行。

示例(五次调用存储过程)

<?php
for ($i=0;$i<5;$i++) {
$statement = $mysqli->stmt_init();
$statement->prepare("CALL some_procedure( ? )");

// 绑定、执行和绑定结果。
$statement->bind_param("i", 1);
$statement->execute();
$statement->bind_result($results);

while(
$statement->fetch()) {
// 处理结果
}

$statement->close();

// 将 mysqli 连接切换到下一个结果集。
while($mysqli->next_result()) { }
}
?>

如果包含最后一条语句,这段代码应该能够执行,不会出现恼人的“Commands out of sync”错误。
lukaszNOSPAMPLEASE at epas dot pl
16年前
如果你们还没发现,我有个坏消息要告诉大家。
使用 mysqli_next_result() 的技巧只能防止存储过程调用后连接断开。
显然,可以为预处理的存储过程调用绑定参数,但是,至少当存储过程本身包含预处理语句时,在 mysqli_stmt_bind_result() 之后,从 mysqli_stmt_fetch() 获取到的记录将会混乱。
避免数据损坏的一种方法是在 mysqli_real_connect() 中指定 CLIENT_MULTI_STATEMENTS 标志,如果它没有被完全禁用(出于安全原因,他们这么说)。另一个选项是使用 mysqli_multi_query(),但是那样就不能绑定参数了。
lachlan76 at gmail dot com
18 年前
不要尝试通过预处理语句使用存储过程。

示例

<?php
$statement
= $mysqli->stmt_init();
$statement->prepare("CALL some_procedure()");
?>

如果尝试这样做,它会在下一个查询期间断开连接而失败。请改用 mysqli_multi_query。

示例

<?php
$mysqli
->multi_query("CALL some_procedure()");
do
{
$result = $mysqli->store_result();

// 在这里进行处理工作

$result->free();
} while(
$mysqli->next_result());
?>

但是,这意味着不能绑定参数或结果。
To Top