Skip to content

MySQL 预处理语句

Prepared statements 是数据库安全的一项关键功能,特别用于防止 SQL 注入 (SQL injection) 漏洞,并且通常能提升性能。

Prepared Statements 和绑定参数 (Bound Parameters)

Section titled “Prepared Statements 和绑定参数 (Bound Parameters)”

Prepared statement 是一个预编译的 SQL 模板,可以使用不同的参数多次执行。过程通常涉及两个阶段:

相对于执行原始 SQL 字符串的优点:

  • 安全性(主要优势): Prepared statements 从根本上防止 SQL 注入。由于 SQL 代码模板和数据值是分开发送的,即使数据包含恶意 SQL 语法,数据库引擎也不会将绑定的数据解释为 SQL 命令。
  • 效率: 数据库只需解析、编译和优化 SQL 查询模板一次。后续使用不同参数的执行会更快,因为它们复用了预编译计划。
  • 降低带宽: 对于重复执行,每次只需将(通常较小的)参数值发送到服务器,而非整个 SQL 查询字符串。

经验法则: 无论何时将用户输入或任何变量数据合并到 SQL 查询中,请始终使用 prepared statements。

MySQLi 支持使用位置占位符 (?) 的 prepared statements。

<?php
$servername = "localhost";
$username = "your_username";
$password = "your_password";
$dbname = "myDB"; // Assumes myDB database exists
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// 1. Prepare the statement with placeholders (?)
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
if ($stmt === false) {
die("Prepare failed: (" . $conn->errno . ") " . $conn->error);
}
// 2. Bind parameters
// "sss" means three string parameters. Use 'i' for integer, 'd' for double, 'b' for blob.
// The variables are passed by reference.
$stmt->bind_param("sss", $firstname, $lastname, $email);
if ($stmt === false) {
die("Bind failed: (" . $stmt->errno . ") " . $stmt->error);
}
// 3. Set parameters and execute for the first record
$firstname = "John";
$lastname = "Doe";
$email = "john.doe@example.com";
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
// 3. Set parameters and execute for the second record
$firstname = "Mary";
$lastname = "Moe";
$email = "mary.moe@example.com";
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
// 3. Set parameters and execute for the third record
$firstname = "Julie";
$lastname = "Dooley";
$email = "julie.dooley@example.com";
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
echo "New records created successfully";
// 4. Close statement and connection
$stmt->close();
$conn->close();
?>

解释:

  • $conn->prepare(...):使用 ? 占位符创建 prepared statement 模板。
  • $stmt->bind_param("sss", ...):将 PHP 变量绑定到占位符。第一个参数 ("sss") 指定每个占位符的数据类型 (s=字符串, i=整数, d=双精度浮点数, b=二进制大对象)。类型字符的数量必须与占位符的数量匹配。
  • $stmt->execute():使用绑定变量的当前值执行 prepared statement。
  • $stmt->close():释放 statement 资源。

PDO 同时支持位置占位符 (?) 和命名占位符 (:name)。命名占位符通常更易读,特别是对于参数较多的查询。

<?php
$servername = "localhost";
$username = "your_username";
$password = "your_password";
$dbname = "myDBPDO"; // Assumes myDBPDO database exists
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// 设置 PDO 错误模式为异常,以便更好地处理错误
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 1. Prepare the statement with named placeholders (:firstname, etc.)
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email)
VALUES (:firstname, :lastname, :email)");
// 绑定参数(也可以直接在 execute 数组中完成)
// $stmt->bindParam(':firstname', $firstname);
// $stmt->bindParam(':lastname', $lastname);
// $stmt->bindParam(':email', $email);
// 设置参数并执行第一条记录(使用 execute 数组)
$firstname = "John";
$lastname = "Doe";
$email = "john.pdo@example.com";
$stmt->execute([':firstname' => $firstname, ':lastname' => $lastname, ':email' => $email]);
// 设置参数并执行第二条记录
$firstname = "Mary";
$lastname = "Moe";
$email = "mary.pdo@example.com";
$stmt->execute([':firstname' => $firstname, ':lastname' => $lastname, ':email' => $email]);
// 设置参数并执行第三条记录
$firstname = "Julie";
$lastname = "Dooley";
$email = "julie.pdo@example.com";
$stmt->execute([':firstname' => $firstname, ':lastname' => $lastname, ':email' => $email]);
echo "New records created successfully using PDO";
} catch(PDOException $e) {
echo "Error: " . $e->getMessage();
}
// 关闭连接(在 PDO 中是可选的,会自动发生)
$conn = null;
?>

解释:

  • $conn->prepare(...):使用命名占位符(例如 :firstname)创建 prepared statement 模板。
  • $stmt->bindParam(':name', $variable):将 PHP 变量绑定到命名占位符。这是按引用绑定。
  • $stmt->bindValue(':name', $value):将特定值绑定到命名占位符。这是按值绑定。
  • $stmt->execute([...]):执行 prepared statement。你可以直接向 execute() 传递一个关联数组,其中的键与命名占位符匹配。这通常是最简洁的方法。
  • 错误处理:try...catch 块可优雅地处理潜在的 PDOException 错误。

无论是使用 MySQLi 还是 PDO,始终使用 prepared statements 是编写安全高效的 PHP 数据库交互代码的基础。