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 模板和参数分开处理:
- 先发送 SQL 模板到数据库,数据库编译并缓存执行计划
- 再发送参数值,数据库将参数作为纯数据填充
- 参数不会被当作 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. 参数绑定
对于需要明确指定数据类型的场景,使用 bindValue 或 bindParam。
<?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_NULL | NULL |
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,都要养成先 prepare 后 execute 的习惯。永远不要将用户输入直接拼入 SQL。
来源:参考了 php-the-right-way「PDO Extension」、PHP 官方文档,改写后所得。