首页 / PHP 入门教程 / MySQL 预处理语句

PHP 入门教程

MySQL 预处理语句

本教程共 65 篇 · 第 55 篇 · 更新于 2026-07-24 · 约 8 分钟阅读

PHPPHP8MySQL预处理语句SQL注入参数绑定安全

55. MySQL 预处理语句

本节目标:理解预处理语句的工作原理,掌握 prepare + execute 的用法,从根本上防止 SQL 注入。

SQL 注入是 Web 应用最常见的安全漏洞之一。预处理语句是防御 SQL 注入的最佳实践,也是现代 PHP 开发的必备技能。


1. 什么是 SQL 注入

如果直接将用户输入拼入 SQL,攻击者可以注入恶意代码:

<?php
// 危险代码!不要这样写
$id = $_GET['id'];
$pdo->query("SELECT * FROM users WHERE id = $id");

攻击者访问 ?id=1 OR 1=1,实际执行的 SQL 变成:

SELECT * FROM users WHERE id = 1 OR 1=1

这会返回所有用户数据。更恶意的注入甚至可以删除数据。


2. 预处理语句原理

预处理语句将 SQL 模板和参数分开处理:

  1. 先发送 SQL 模板到数据库,数据库编译并缓存执行计划
  2. 再发送参数值,数据库将参数作为纯数据填充
  3. 参数不会被当作 SQL 代码解析,注入攻击自然失效

3. PDO 预处理示例

命名占位符

<?php
$pdo = new PDO('mysql:host=localhost;dbname=my_shop;charset=utf8mb4', 'root', '123456');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 准备 SQL 模板
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status");

// 执行时传入参数
$stmt->execute([
    ':email'  => 'zhang@example.com',
    ':status' => 1
]);

$user = $stmt->fetch(PDO::FETCH_ASSOC);

问号占位符

<?php
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND status = ?");
$stmt->execute(['zhang@example.com', 1]);

$user = $stmt->fetch(PDO::FETCH_ASSOC);
Tip

命名占位符可读性更好,推荐优先使用。问号占位符在参数较多时容易数错位置。


4. 参数绑定

对于需要明确指定数据类型的场景,使用 bindValuebindParam

<?php
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id AND age > :min_age");

// bindValue:绑定具体值
$stmt->bindValue(':id', 5, PDO::PARAM_INT);
$stmt->bindValue(':min_age', 18, PDO::PARAM_INT);
$stmt->execute();

// bindParam:绑定变量引用(变量值改变后重新执行生效)
$minAge = 18;
$stmt = $pdo->prepare("SELECT * FROM users WHERE age > :min_age");
$stmt->bindParam(':min_age', $minAge, PDO::PARAM_INT);
$stmt->execute(); // 查询 age > 18

$minAge = 30;
$stmt->execute(); // 再次执行,查询 age > 30

常用参数类型

常量说明
PDO::PARAM_INT整数
PDO::PARAM_STR字符串(默认)
PDO::PARAM_BOOL布尔值
PDO::PARAM_NULLNULL

5. mysqli 预处理示例

<?php
$mysqli = new mysqli('localhost', 'root', '123456', 'my_shop');
$mysqli->set_charset('utf8mb4');

$stmt = $mysqli->prepare("SELECT id, username FROM users WHERE age > ?");
$stmt->bind_param('i', $minAge); // 'i' 表示整数

$minAge = 18;
$stmt->execute();

$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    echo $row['username'] . "<br>";
}

$stmt->close();

bind_param 类型标记

标记类型
i整数
d双精度浮点数
s字符串
b二进制数据

6. 批量插入

预处理语句特别适合批量操作,只需准备一次,多次执行:

<?php
$users = [
    ['张三', 'zhang@example.com'],
    ['李四', 'li@example.com'],
    ['王五', 'wang@example.com'],
];

$stmt = $pdo->prepare("INSERT INTO users (username, email) VALUES (:name, :email)");

foreach ($users as $user) {
    $stmt->execute([':name' => $user[0], ':email' => $user[1]]);
}
Note

批量插入用预处理循环执行,比拼接单条 SQL 更安全,且能复用执行计划,性能更好。


小结

预处理语句是数据库操作的唯一安全方式。无论使用 PDO 还是 mysqli,都要养成先 prepareexecute 的习惯。永远不要将用户输入直接拼入 SQL。


来源:参考了 php-the-right-way「PDO Extension」、PHP 官方文档,改写后所得。