PHP 基础教程
PHP 高级教程
PHP & MySQL DATABASE
PHP 示例
PHP 参考手册

PHP MySQL Prepared 语句

在本教程中,您将学习如何使用 PHP 在 MySQL 中使用准备好的语句。

什么是预备语句

准备好的语句(也称为参数化语句)只是一个包含占位符而不是实际参数值的 SQL 查询模板。 这些占位符将在执行语句时被实际值替换。

MySQLi 支持使用匿名位置占位符(?),如下图:

INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?);

同时,PDO 支持匿名位置占位符 (?) 以及命名占位符。 命名占位符以冒号 (:) 开头,后跟标识符,如下所示:

INSERT INTO persons (first_name, last_name, email)
VALUES (:first_name, :last_name, :email);

准备好的语句执行包括两个阶段:准备和执行。

  • 准备 — 在准备阶段,会创建一个 SQL 语句模板并将其发送到数据库服务器。 服务器解析语句模板,执行语法检查和查询优化,并将其存储以供以后使用。
  • 执行 — 在执行期间,参数值被发送到服务器。 服务器从语句模板和这些值创建一个语句来执行它。

准备好的语句非常有用,尤其是在您使用不同的值多次执行特定语句的情况下,例如,一系列 INSERT 语句。 以下部分描述了使用它的一些主要好处。

使用预备语句的优点

预备语句可以高效地重复执行同一条语句,因为该语句只被解析一次,而它可以被执行多次。 它还最大限度地减少带宽使用,因为每次执行时只需将占位符值传输到数据库服务器,而不是完整的 SQL 语句。

预备语句还提供了针对 SQL 注入的强大保护,因为参数值不直接嵌入到 SQL 查询字符串中。 参数值使用不同的协议与查询分开发送到数据库服务器,因此不会干扰它。 在解析语句模板之后,服务器直接在执行点使用这些值。 这就是准备好的语句不易出错的原因,因此被认为是数据库安全中最关键的元素之一。

以下示例将向您展示准备好的语句实际上是如何工作的:

示例

Procedural Object Oriented PDO
Download
<?php
/* 尝试 MySQL 服务器连接。 假设您正在运行 MySQL
具有默认设置的服务器(用户 'root' 没有密码) */
$link = mysqli_connect("localhost", "root", "", "demo");
 
// 检查连接
if($link === false){
    die("ERROR: Could not connect. " . mysqli_connect_error());
}
 
// 准备一个插入语句
$sql = "INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)";
 
if($stmt = mysqli_prepare($link, $sql)){
    // 将变量作为参数绑定到准备好的语句
    mysqli_stmt_bind_param($stmt, "sss", $first_name, $last_name, $email);
    
    /* 设置参数值并执行
   该语句再次插入另一行 */
    $first_name = "Hermione";
    $last_name = "Granger";
    $email = "hermionegranger@mail.com";
    mysqli_stmt_execute($stmt);
    
    /* 设置参数值并执行
    插入行的语句 */
    $first_name = "Ron";
    $last_name = "Weasley";
    $email = "ronweasley@mail.com";
    mysqli_stmt_execute($stmt);
    
    echo "Records inserted successfully.";
} else{
    echo "ERROR: Could not prepare query: $sql. " . mysqli_error($link);
}
 
// 关闭语句
mysqli_stmt_close($stmt);
 
// 关闭连接
mysqli_close($link);
?>
<?php
/* 尝试 MySQL 服务器连接。 假设您正在运行 MySQL
具有默认设置的服务器(用户 'root' 没有密码) */
$mysqli = new mysqli("localhost", "root", "", "demo");
 
// 检查连接
if($mysqli === false){
    die("ERROR: Could not connect. " . $mysqli->connect_error);
}
 
// 准备一个插入语句
$sql = "INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)";
 
if($stmt = $mysqli->prepare($sql)){
    // 将变量作为参数绑定到准备好的语句
    $stmt->bind_param("sss", $first_name, $last_name, $email);
    
    /* 设置参数值并执行
   该语句再次插入另一行 */
    $first_name = "Hermione";
    $last_name = "Granger";
    $email = "hermionegranger@mail.com";
    $stmt->execute();
    
    /* 设置参数值并执行
    插入行的语句 */
    $first_name = "Ron";
    $last_name = "Weasley";
    $email = "ronweasley@mail.com";
    $stmt->execute();
    
    echo "Records inserted successfully.";
} else{
    echo "ERROR: Could not prepare query: $sql. " . $mysqli->error;
}
 
// 关闭语句
$stmt->close();
 
// 关闭连接
$mysqli->close();
?>
<?php
/* 尝试 MySQL 服务器连接。 假设您正在运行 MySQL
具有默认设置的服务器(用户 'root' 没有密码) */
try{
    $pdo = new PDO("mysql:host=localhost;dbname=demo", "root", "");
    // 设置 PDO 错误模式为异常
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
    die("ERROR: Could not connect. " . $e->getMessage());
}
 
// 尝试插入查询执行
try{
    // 准备一个插入语句
    $sql = "INSERT INTO persons (first_name, last_name, email) VALUES (:first_name, :last_name, :email)";
    $stmt = $pdo->prepare($sql);
    
    // 将参数绑定到语句
    $stmt->bindParam(':first_name', $first_name, PDO::PARAM_STR);
    $stmt->bindParam(':last_name', $last_name, PDO::PARAM_STR);
    $stmt->bindParam(':email', $email, PDO::PARAM_STR);
    
    /* 设置参数值并执行
   该语句再次插入另一行 */
    $first_name = "Hermione";
    $last_name = "Granger";
    $email = "hermionegranger@mail.com";
    $stmt->execute();
    
    /* 设置参数值并执行
    插入行的语句 */
    $first_name = "Ron";
    $last_name = "Weasley";
    $email = "ronweasley@mail.com";
    $stmt->execute();
    
    echo "Records inserted successfully.";
} catch(PDOException $e){
    die("ERROR: Could not prepare/execute query: $sql. " . $e->getMessage());
}
 
// 关闭语句
unset($stmt);
 
// 关闭连接
unset($pdo);
?>

正如您在上面的示例中所见,我们只准备了一次 INSERT 语句,但通过传递不同的参数集多次执行它。

代码解释(程序风格)

在上面示例的 SQL INSERT 语句(line no-12)中,问号用作 first_name, last_name, email 字段值的占位符。

mysqli_stmt_bind_param() 函数(line no-16)将变量绑定到 SQL 语句模板中的占位符 (?)。 占位符 (?) 将替换为执行时变量中保存的实际值。 作为第二个参数提供的 type 定义字符串,即"sss"字符串指定每个绑定变量的数据类型是字符串。

类型定义字符串指定对应绑定变量的数据类型,包含以下四个字符中的一个或多个:

  • b — 二进制(如图片、PDF文件等)
  • d — double(浮点数)
  • i — 整数(整数)
  • s — 字符串(文本)

绑定变量的数量和类型定义字符串中的字符数量必须与 SQL 语句模板中的占位符数量相匹配。


使用通过 Web 表单接收的输入

如果你还记得上一章,我们已经为 将数据插入数据库 创建了一个 HTML 表单。 在这里,我们将通过实现准备好的语句来扩展该示例。 您可以使用相同的 HTML 表单来测试以下插入脚本示例,但只需确保您在表单的 action 属性中使用了正确的文件名。

这是用于插入数据的更新的 PHP 代码。 如果你仔细看这个例子,你会发现我们没有使用 mysqli_real_escape_string() 来转义用户输入,就像我们在前一章的例子中所做的那样。 由于在准备好的语句中,用户输入永远不会直接替换到查询字符串中,因此它们不需要正确转义。

示例

Procedural Object Oriented PDO
Download
<?php
/* 尝试 MySQL 服务器连接。 假设您正在运行 MySQL
具有默认设置的服务器(用户 'root' 没有密码) */
$link = mysqli_connect("localhost", "root", "", "demo");
 
// 检查连接
if($link === false){
    die("ERROR: Could not connect. " . mysqli_connect_error());
}
 
// 准备一个插入语句
$sql = "INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)";
 
if($stmt = mysqli_prepare($link, $sql)){
    // 将变量作为参数绑定到准备好的语句
    mysqli_stmt_bind_param($stmt, "sss", $first_name, $last_name, $email);
    
    // 设置参数
    $first_name = $_REQUEST['first_name'];
    $last_name = $_REQUEST['last_name'];
    $email = $_REQUEST['email'];
    
    // 尝试执行准备好的语句
    if(mysqli_stmt_execute($stmt)){
        echo "Records inserted successfully.";
    } else{
        echo "ERROR: Could not execute query: $sql. " . mysqli_error($link);
    }
} else{
    echo "ERROR: Could not prepare query: $sql. " . mysqli_error($link);
}
 
// 关闭语句
mysqli_stmt_close($stmt);
 
// 关闭连接
mysqli_close($link);
?>
<?php
/* 尝试 MySQL 服务器连接。 假设您正在运行 MySQL
具有默认设置的服务器(用户 'root' 没有密码) */
$mysqli = new mysqli("localhost", "root", "", "demo");
 
// 检查连接
if($mysqli === false){
    die("ERROR: Could not connect. " . $mysqli->connect_error);
}
 
// 准备一个插入语句
$sql = "INSERT INTO persons (first_name, last_name, email) VALUES (?, ?, ?)";
 
if($stmt = $mysqli->prepare($sql)){
    // 将变量作为参数绑定到准备好的语句
    $stmt->bind_param("sss", $first_name, $last_name, $email);
    
    // 设置参数
    $first_name = $_REQUEST['first_name'];
    $last_name = $_REQUEST['last_name'];
    $email = $_REQUEST['email'];
    
    // 尝试执行准备好的语句
    if($stmt->execute()){
        echo "Records inserted successfully.";
    } else{
        echo "ERROR: Could not execute query: $sql. " . $mysqli->error;
    }
} else{
    echo "ERROR: Could not prepare query: $sql. " . $mysqli->error;
}
 
// 关闭语句
$stmt->close();
 
// 关闭连接
$mysqli->close();
?>
<?php
/* 尝试 MySQL 服务器连接。 假设您正在运行 MySQL
具有默认设置的服务器(用户 'root' 没有密码) */
try{
    $pdo = new PDO("mysql:host=localhost;dbname=demo", "root", "");
    // 设置 PDO 错误模式为异常
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
    die("ERROR: Could not connect. " . $e->getMessage());
}
 
// 尝试插入查询执行
try{
    // 准备一个插入语句
    $sql = "INSERT INTO persons (first_name, last_name, email) VALUES (:first_name, :last_name, :email)";
    $stmt = $pdo->prepare($sql);
    
    // 将参数绑定到语句
    $stmt->bindParam(':first_name', $_REQUEST['first_name'], PDO::PARAM_STR);
    $stmt->bindParam(':last_name', $_REQUEST['last_name'], PDO::PARAM_STR);
    $stmt->bindParam(':email', $_REQUEST['email'], PDO::PARAM_STR);
    
    // 执行准备好的语句
    $stmt->execute();
    echo "Records inserted successfully.";
} catch(PDOException $e){
    die("ERROR: Could not prepare/execute query: $sql. " . $e->getMessage());
}
 
// 关闭语句
unset($stmt);
 
// 关闭连接
unset($pdo);
?>

注意:虽然在准备好的语句中不需要转义用户输入,但您应该始终验证从外部来源接收的数据的类型和大小,并实施适当的限制以防止系统资源被利用。

Advertisements