PHP Conference Japan 2024

mysqli::prepare

mysqli_prepare

(PHP 5, PHP 7, PHP 8)

mysqli::prepare -- mysqli_prepare准备 SQL 语句以供执行

描述

面向对象风格

public mysqli::prepare(string $query): mysqli_stmt|false

过程式风格

mysqli_prepare(mysqli $mysql, string $query): mysqli_stmt|false

准备 SQL 查询,并返回一个语句句柄,用于对语句进行进一步操作。查询必须包含单个 SQL 语句。

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

参数

mysql

仅过程式风格:由 mysqli_connect()mysqli_init() 返回的 mysqli 对象

query

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

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

注意:

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

返回值

mysqli_prepare() 返回一个语句对象或 false(如果发生错误)。

错误/异常

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

示例

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

面向对象风格

<?php

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

$city = "Amersfoort";

/* 创建预处理语句 */
$stmt = $mysqli->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_prepare($link, "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

参见

添加注释

用户贡献的注释 20 条注释

timchampion dot NOSPAM at gmail dot com
12 年前
只想确保大家都能了解 get_result。

在代码示例中,在 execute() 之后,执行 get_result(),如下所示

<?php

// ... 文档示例代码:

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

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

/* 替代 bind_result: */
$result = $stmt->get_result();

/* 现在您可以将结果提取到数组中 - 很棒 */
while ($myrow = $result->fetch_assoc()) {

// 使用 $myrow 数组,就像使用其他 fetch 一样
printf("%s 位于 %s 区\n", $city, $myrow['district']);

}
?>

当您的查询返回十几个或更多字段时,这种方法更好。希望对您有所帮助。
Darren
12 年前
我编写了这个函数供自己使用,并认为可以分享一下。我不确定这个论坛是否合适,但我希望在我偶然发现 mysqli::prepare 时就有这个函数。这个函数是我之前发布的函数的更新版本。之前的函数无法处理多个查询。

对于查询
单个查询的结果以数组形式给出:[行号][关联数据数组]
多个查询的结果以数组形式给出:[查询号][行号][关联数据数组]

对于返回受影响行数的查询,返回受影响的行数而不是(数组[行号][关联数据数组])

代码和示例如下

<?php
function mysqli_prepared_query($link,$sql,$typeDef = FALSE,$params = FALSE){
if(
$stmt = mysqli_prepare($link,$sql)){
if(
count($params) == count($params,1)){
$params = array($params);
$multiQuery = FALSE;
} else {
$multiQuery = TRUE;
}

if(
$typeDef){
$bindParams = array();
$bindParamsReferences = array();
$bindParams = array_pad($bindParams,(count($params,1)-count($params))/count($params),"");
foreach(
$bindParams as $key => $value){
$bindParamsReferences[$key] = &$bindParams[$key];
}
array_unshift($bindParamsReferences,$typeDef);
$bindParamsMethod = new ReflectionMethod('mysqli_stmt', 'bind_param');
$bindParamsMethod->invokeArgs($stmt,$bindParamsReferences);
}

$result = array();
foreach(
$params as $queryKey => $query){
foreach(
$bindParams as $paramKey => $value){
$bindParams[$paramKey] = $query[$paramKey];
}
$queryResult = array();
if(
mysqli_stmt_execute($stmt)){
$resultMetaData = mysqli_stmt_result_metadata($stmt);
if(
$resultMetaData){
$stmtRow = array();
$rowReferences = array();
while (
$field = mysqli_fetch_field($resultMetaData)) {
$rowReferences[] = &$stmtRow[$field->name];
}
mysqli_free_result($resultMetaData);
$bindResultMethod = new ReflectionMethod('mysqli_stmt', 'bind_result');
$bindResultMethod->invokeArgs($stmt, $rowReferences);
while(
mysqli_stmt_fetch($stmt)){
$row = array();
foreach(
$stmtRow as $key => $value){
$row[$key] = $value;
}
$queryResult[] = $row;
}
mysqli_stmt_free_result($stmt);
} else {
$queryResult[] = mysqli_stmt_affected_rows($stmt);
}
} else {
$queryResult[] = FALSE;
}
$result[$queryKey] = $queryResult;
}
mysqli_stmt_close($stmt);
} else {
$result = FALSE;
}

if(
$multiQuery){
return
$result;
} else {
return
$result[0];
}
}
?>

示例
对于一个包含 firstName 和 lastName 的表格
John Smith
Mark Smith
Jack Johnson
Bob Johnson

<?php
// 单个查询,单个结果
$query = "SELECT * FROM names WHERE firstName=? AND lastName=?";
$params = array("Bob","Johnson");

mysqli_prepared_query($link,$query,"ss",$params)
/*
返回数组:
0=> array('firstName' => 'Bob', 'lastName' => 'Johnson')
)
*/

// 单个查询,多个结果
$query = "SELECT * FROM names WHERE lastName=?";
$params = array("Smith");

mysqli_prepared_query($link,$query,"s",$params)
/*
返回数组:
0=> array('firstName' => 'John', 'lastName' => 'Smith')
1=> array('firstName' => 'Mark', 'lastName' => 'Smith')
)
*/

// 多个查询,多个结果
$query = "SELECT * FROM names WHERE lastName=?";
$params = array(array("Smith"),array("Johnson"));

mysqli_prepared_query($link,$query,"s",$params)
/*
返回数组:
0=>
array(
0=> array('firstName' => 'John', 'lastName' => 'Smith')
1=> array('firstName' => 'Mark', 'lastName' => 'Smith')
)
1=>
array(
0=> array('firstName' => 'Jack', 'lastName' => 'Johnson')
1=> array('firstName' => 'Bob', 'lastName' => 'Johnson')
)
)
*/
?>

希望对您有所帮助 =)
urso at email dot cz
2年前
不幸的是,使用“/* bind result variables */ $stmt->bind_result($district);”已经过时且不被推荐。

<?php
$mysqli
= new mysqli("localhost", "test", "test", "test");
if (
$mysqli->character_set_name()!="utf8mb4") { $mysqli->set_charset("utf8mb4"); }
$secondname = "Ma%";
$types = "s";
$parameters = array($secondname);
$myquery = "select * from users where secondname like ?";
if (
$stmt = $mysqli->prepare($myquery)) {
$stmt->bind_param($types, ...$parameters);
$stmt->execute();
$result = $stmt->get_result();
$stmt->close();
$numrows = $result->num_rows;
while(
$row = $result->fetch_assoc()) {
echo
$row['firstname']." ".$row['secondname']."<br />";
}
}
$mysqli->close();
?>

此外,不要使用`'$stmt->bind_param("s", $city);'`,而应使用`"$stmt->bind_param($types, ...$parameters);"` 配合数组。此处使用数组(`$parameters`)的优势已显而易见,它用一个包含5个元素的数组取代了5个变量。

<?php
$mysqli
= new mysqli("localhost", "test", "test", "test");
if (
$mysqli->character_set_name()!="utf8mb4") { $mysqli->set_charset("utf8mb4"); }
$uid = intval($_POST['uid']);
$length=15; $account = mb_substr(trim($_POST['account']),0,$length,"utf-8"); $account=strip_tags($account);
$length=50; $password = mb_substr(trim($_POST['password']),0,$length,"utf-8"); $password = password_hash($password, PASSWORD_DEFAULT);
$length=25; $prijmeni = mb_substr(trim($_POST['prijmeni']),0,$length,"utf-8"); $prijmeni=strip_tags($prijmeni);
$length=25; $firstname = mb_substr(trim($_POST['firstname']),0,$length,"utf-8"); $firstname=strip_tags($firstname); $firstname = str_replace(array(">","<",'"'), array("","",""), $firstname);
$dotaz = "UPDATE users SET account = ?, password = ?, secname = ?, firstname = ? WHERE uid = ?";
$types = "ssssi";
$parameters = array($account,$password,$prijmeni,$firstname,$uid);
if (
$stmt = $mysqli->prepare($dotaz)) {
$stmt->bind_param($types, ...$parameters);
$stmt->execute();
echo
$stmt->affected_rows;
$stmt->close();
}
$mysqli->close();
?>
kritz at hrz dot tu-chemnitz dot de
7年前
由于我当前工作的服务器缺少允许我在 `mysqli_stmt` 上调用 `get_result` 的 PHP 模块,我无法完全测试以下内容,但这可能对其他人有所帮助。

<?php

/**
* 具有附加功能的自定义 {@link \mysqli} 类。
*/
class CustomMysqli extends \mysqli
{
/**
* 创建准备好的查询,绑定给定的参数并返回执行的 {@link \mysqli_stmt} 的结果。
* @param string $query
* @param array $args
* @return bool|\mysqli_result
*/
public function queryPrepared($query, array $args)
{
$stmt = $this->prepare($query);
$params = [];
$types = array_reduce($args, function ($string, &$arg) use (&$params) {
$params[] = &$arg;
if (
is_float($arg)) $string .= 'd';
elseif (
is_integer($arg)) $string .= 'i';
elseif (
is_string($arg)) $string .= 's';
else
$string .= 'b';
return
$string;
},
'');
array_unshift($params, $types);

call_user_func_array([$stmt, 'bind_param'], $params);

$result = $stmt->execute() ? $stmt->get_result() : false;

$stmt->close();

return
$result;
}
}

$db = new CustomMysqli('host', 'user', 'password', 'database', 3306);
$result = $db->queryPrepared(
'SELECT * FROM table WHERE something = ? AND someotherthing = ? AND elsewhat = ?',
[
'dunno',
1,
'dontcare'
]
);

if (isset(
$result) && $result instanceof \mysqli_result) {
while (
null !== ($row = $result->fetch_assoc())) {
echo
'<pre>'.var_debug($row, true).'</pre>';
}
}

?>

注意:如果你想在低于 5.4 的 PHP 版本中使用它,你必须使用旧的、难看的 `array()` 语法来表示数组,而不是简短的 `[]` 语法。
admin at xorath dot com
17年前
性能说明,给那些想知道的人。我进行了一项测试,首先插入大约 30,000 条帖子,其中一个主键:id 和一个 varchar(20),varchar 数据是当前迭代值的 md5 哈希值,只是为了填充一些数据。

测试在一个带有 Apache2/PHP5/MySQL5.0 的专用 Ubuntu 7.04 服务器上进行,该服务器运行在 Athlon 64 - 3000+ 上,具有 512MB 的 RAM。使用从 0 到 30000 的 for 循环测试查询,首先使用...

<?php
for ( $i = 0; $i <= 30000; ++$i )
{
$result = $mysqli->query("SELECT * FROM test WHERE id = $i");
$row = $result->fetch_row();
echo
$row[0]; //输出id
}
?>

这段代码的平均页面加载时间约为3.3秒,然后使用下面的循环

<?php
$stmt
= $mysqli->prepare("SELECT * FROM test WHERE id = ?");
for (
$i = 0; $i <= 30000; ++$i )
{
$stmt->bind_param("i", $i);
$stmt->execute();
$stmt->bind_result($id, $md5);
$stmt->fetch();
echo
$id;
}
$stmt->close();
?>

平均页面加载时间降低了1.3秒,这意味着平均约为2.0秒!可以推测,在更复杂/更大的表和更复杂的SQL查询中,性能差异可能会更大。
Codeguy
13年前
在SQL中使用预处理语句的实际目的是降低查询处理的成本;而不是将数据与查询分离。这就是现在PHP中使用它的方式,而不是它最初的设计用途。使用SQL,您可以通过使用预处理语句来降低执行多个相似查询的成本。这样做可以减少解析、验证,并且通常会预先生成该查询的执行计划。这就是为什么它们在循环中运行速度比它们的直接查询同类快的原因。不要假设仅仅因为有人以这种方式使用PHP和此函数就意味着这是唯一的方法或唯一的方法。虽然它比一般的查询更安全,但它们在可以执行的操作或更精确地说你如何执行操作方面也更加有限。
cdtreeks at gmail dot com
9年前
执行预处理MySQL时,默认情况下,如果出现错误,则您的prepare()调用只会返回FALSE。

要获取完整的MySQL错误,请在准备查询之前创建一个语句对象,如下所示

<?php
$mysqli
= new mysqli("localhost", "my_user", "my_password", "world");

/* 检查连接 */
if (mysqli_connect_errno()) {
printf("连接失败: %s\n", mysqli_connect_error());
exit();
}

$city = "Amersfoort";

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

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

/* 执行查询 */
if (!$statement->execute()) {
trigger_error('执行MySQL查询出错: ' . $statement->error);
}

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

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

printf("%s位于%s区\n", $city, $district);

/* 关闭语句 */
$statement->close();
}

/* 关闭连接 */
$mysqli->close();
?>
omidbahrami1990 at gmail dot com
6年前
这是一个安全地使用mysqli::prepare的方法
--------------------------------------------------------
<?php
function secured_signup($username,$password)
{
$connection = new mysqli($dbhost,$dbusername,$dbpassword,$dbname);
if (
$connection->connect_error)
die(
"Secured");

$prepared = $connection->prepare("INSERT INTO `users` ( `username` , `password` ) VALUES ( ? , ? ) ; ");
if(
$prepared==false)
die(
"Secured");

$result=$prepared->bind_param("ss",$username,$password);
if(
$result==false)
die(
"Secured");

$result=$prepared->execute();
if(
$result==false)
die(
"Secured");

$prepared->close();
$connection->close();
}
/*
$dbhost ---> 数据库IP地址
$dbusername ---> 数据库用户名
$dbpassword ---> 数据库密码
$dbname ---> 数据库名称
*/
?>
David Kramer
18年前
我认为这些不是很好的例子,因为预处理查询的主要用途是在要循环调用同一查询,每次插入不同的值时。例如,如果您正在生成报表并且需要为每一行运行相同的查询,调整WHERE子句中的值,或从另一个系统导入数据。
Darren
12 年前
对于那些第一次学习mysqli::prepare和mysqli_stmt::bind_params的人,这里有一段带注释的代码块,它执行预处理查询并以与mysqli_query的返回值类似的格式返回数据。我尽量减少不必要的类、对象或开销,原因有两个
1) 促进学习
2) 允许与mysqli_query相对互换使用

我的目标是降低刚开始使用这些函数系列的人的学习曲线。我绝不是一个专业的编码器/脚本编写者,所以我肯定有一些改进之处,也许还有一些错误,但我希望没有 =)

<?php
/*
函数: mysqli_prepared_query()
执行准备好的查询语句,并绑定参数
以数组格式返回数据

参数:
mysqli 连接
mysqli 准备好的查询语句
mysqli_stmt_bind_param 参数列表,形式为数组($typeDefinitinonString, $var1 [, mixed $... ])

返回值:
对于 SELECT、SHOW、DESCRIBE 或 EXPLAIN 语句:以 resultArray[行号][关联字段名] 的形式返回表数据
对于其他查询语句,返回受影响的行数
出错时返回 FALSE
*/
function mysqli_prepared_query($link,$sql,$bindParams = FALSE){
if(
$stmt = mysqli_prepare($link,$sql)){
if (
$bindParams){
$bindParamsMethod = new ReflectionMethod('mysqli_stmt', 'bind_param'); //允许使用可变参数列表调用 mysqli_stmt->bind_param
$bindParamsReferences = array(); //将作为 mysqli_stmt->bind_param 的参数列表

$typeDefinitionString = array_shift($bindParams);
foreach(
$bindParams as $key => $value){
$bindParamsReferences[$key] = &$bindParams[$key];
}

array_unshift($bindParamsReferences,$typeDefinitionString); //将 typeDefinition 作为字符串的第一个元素返回
$bindParamsMethod->invokeArgs($stmt,$bindParamsReferences); //使用 $bindParamsRereferences 作为参数列表调用 mysqli_stmt->bind_param
}
if(
mysqli_stmt_execute($stmt)){
$resultMetaData = mysqli_stmt_result_metadata($stmt);
if(
$resultMetaData){
$stmtRow = array(); //这将是 mysqli_stmt_fetch($stmt) 返回的结果行
$rowReferences = array(); //这将引用 $stmtRow 并传递给 mysqli_bind_results
while ($field = mysqli_fetch_field($resultMetaData)) {
$rowReferences[] = &$stmtRow[$field->name];
}
mysqli_free_result($resultMetaData);
$bindResultMethod = new ReflectionMethod('mysqli_stmt', 'bind_result');
$bindResultMethod->invokeArgs($stmt, $rowReferences); //使用面向对象的方式调用 mysqli_stmt_bind_result($stmt,[$rowReferences])
$result = array();
while(
mysqli_stmt_fetch($stmt)){
foreach(
$stmtRow as $key => $value){ //变量必须按值赋值,因此 $result[] = $stmtRow 不起作用(不太确定原因,可能与 $stmtRow 中的引用有关)
$row[$key] = $value;
}
$result[] = $row;
}
mysqli_stmt_free_result($stmt);
} else {
$result = mysqli_stmt_affected_rows($stmt);
}
mysqli_stmt_close($stmt);
} else {
$result = FALSE;
}
} else {
$result = FALSE;
}
return
$result;
}

?>

希望 PHP 神不会惩罚我。
Bernie van&#39;t Hof
12 年前
预处理语句一开始很令人困惑……

mysqli->prepare() 返回一个所谓的语句对象,用于后续操作,例如 execute、bind_param、store_result、bind_result、fetch 等。

语句对象具有私有属性,这些属性会在执行每个语句操作时更新。我发现这些属性对于理解编写预处理语句函数时的运行过程很有用。

受影响的行数
自增 ID
行数
参数计数
字段计数
错误号
错误信息
SQL 状态
ID

但我花了一点时间才弄清楚如何访问它们。

<?php
$stmt
= $mysqli->prepare($query);

// .. $stmt-> 操作 ..

var_dump($stmt); // 显示空值

var_dump($stmt->errno); // 注意字面量,显示值

// .. $stmt-> 操作 ..

// 保留副本 ..
// get_object_properties() 不起作用
// clone() 不起作用
$properties = array();
foreach (
$stmt as $name => $priv){
$properties[$name] = $stmt->$name; // 起作用
// $properties[$name] = $priv; // 不起作用,foreach 无法访问私有属性
}

$stmt->close();
// var_dump($stmt->errno) // 不起作用,$stmt 已关闭
?>
REz
10 年前
没有资料表明所有数据必须在新的 msqli prepare 调用之前全部提取,唯一有用的信息是一个 6 年前的评论!
必须使用 myslqi_stmt::fetch() 提取数据,直到返回 NULL,才能再次调用 mysqli::prepare(),否则 mysqli::$errno 和 mysqli::$error 中将出现 FALSE 和无错误信息。
Adam
18年前
预处理语句的目的是不在 SQL 语句中包含数据。在 SQL 语句中包含数据是不安全的。始终使用预处理语句。它们使用起来更简洁(代码更容易阅读)并且不易受到 SQL 注入攻击。

转义字符串以包含在 SQL 语句中在某些区域设置中效果不佳,因此不安全。
codeFiend <aeontech at gmail dot com>
18年前
请注意,参数标记周围的单引号会阻止正确准备语句。
例如

<?php
$stmt
= $mysqli->prepare("INSERT INTO City (District) VALUES ('?')");
echo
$stmt->param_count." parameters\n";
?>
将打印 0 并失败,并在尝试将变量绑定到它时出现“变量数量与预处理语句中的参数数量不匹配”警告。

但是

<?php
$stmt
= $mysqli->prepare("INSERT INTO City (District) VALUES (?)");
echo
$stmt->param_count." parameters\n";
?>
将打印 1 并正常运行。

非常烦人,我花了一个小时才弄清楚这一点。
wapharshitsingh at gmail dot com
3 年前
只想在这里分享一下,如何有效地结合 ... 运算符使用预处理语句。
<?php
class Database{
private function
getTypeofValues($string, $value){
if(
is_float($value)){
$string .= "d";
}elseif(
is_integer($value)){
$string .= "i";
}elseif(
is_string($value)){
$string .= "s";
}else{
$string .= "b";
}
return
$string;
}
public function
makeQuery($query, array $values){
$stmt = $this->connection->prepare($query);
$type = array_reduce($values, array($this, "getTypeOfValues"));
$stmt->bind_param($type, ...$values);
$stmt->execute();
$result = $stmt->get_result();
return
$result;
}
}
?>
rafael at stiod dot com
16 年前
所有数据必须在新的语句准备之前全部提取。
@runspired
11 年前
我认为这不是一个错误,而是一种意外的行为。在构建 API 时,我发现将 INT 0 而不是 STRING '0' 传递给预处理语句会导致我的脚本内存耗尽并在网页上产生 500 错误。

下面是一个简化的示例:(`$_DB` 是一个指向 mysqli 连接的全局引用)

<?php
function getItem( $ID ) {

$_STATEMENT = $_DB->prepare("SELECT item_user, item_name, item_description FROM item WHERE item_id = ?;");

$_STATEMENT->bind_param( 'i' , $ID );

$_STATEMENT->execute();
$_STATEMENT->store_result();

$_STATEMENT->bind_result( $user , $name , $description);
$result = $_STATEMENT->fetch();

$_STATEMENT->free_result();
$_STATEMENT->close();

return
$result;
}

getItem(0); //失败!
getItem('0'); //成功!

?>

我猜测可能是整数 0 被解释为布尔值,如果真是这样,应该在文档中说明,但是所有尝试通过 php 脚本获取错误信息的方法都失败了。
sdepouw at NOSPAM dot com
15 年前
我不知道这对其他人来说是否显而易见,但是如果你尝试为数据库当前指向的数据库中不存在的表准备查询(或者如果你的查询以其他方式无效,我想),则不会返回对象。只有在我不断收到致命错误(说我的语句变量未设置为对象的实例(它可能为 null))后进行一些挖掘之后才注意到这一点。

将 NOSPAM 替换为 nimblepros 以向我发送电子邮件。
Zeebuck
13年前
我认为它最初的构建目的和当今人们使用它的目的已经有所不同。但是为什么要纠结于最初的目的呢?显然,如今在预处理语句中添加了更多代码以使其能够用于防止 sql 注入,因此它现在也是当今设计目的的一部分,以及可重复语句的性能。
marmstro at gmail dot com
11 年前
如果你的 IDE 在使用传统的 prepare 方法时无法识别 $stmt 为 mysqli_stmt 类型对象

$stmt = mysqli_prepare($link, $query);

以下方法有效且 IDE 友好

$stmt = new mysqli_stmt($link, $query);
To Top