本文目录导读:

- 使用 PDO(PHP Data Objects)【推荐】
- 使用 MySQLi(MySQL Improved Extension)
- PDO vs MySQLi 对比
- 最佳实践与注意事项
- 完整的 PDO 封装示例
在 PHP 中,参数化查询(Parameterized Query)是防止 SQL 注入的最佳实践,它通过将 SQL 语句与数据分离,让数据库引擎预编译 SQL 模板,然后安全地绑定参数值。
以下是 PHP 中实现参数化查询的三种主流方式:
使用 PDO(PHP Data Objects)【推荐】
PDO 提供了统一的接口来访问多种数据库。
基本示例(MySQL)
<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$username = 'root';
$password = '';
try {
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 参数化查询:使用命名占位符
$sql = "SELECT * FROM users WHERE email = :email AND status = :status";
$stmt = $pdo->prepare($sql);
// 绑定参数
$stmt->bindParam(':email', $email);
$stmt->bindParam(':status', $status);
$email = 'user@example.com';
$status = 'active';
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($results);
} catch (PDOException $e) {
echo "数据库错误: " . $e->getMessage();
}
?>
使用问号占位符(位置参数)
$sql = "SELECT * FROM users WHERE email = ? AND status = ?"; $stmt = $pdo->prepare($sql); $stmt->execute(['user@example.com', 'active']); // 或者 $stmt->execute([$email, $status]);
插入数据示例
$sql = "INSERT INTO users (name, email, age) VALUES (:name, :email, :age)";
$stmt = $pdo->prepare($sql);
$data = [
':name' => '张三',
':email' => 'zhangsan@example.com',
':age' => 25
];
$stmt->execute($data);
echo "插入成功,ID: " . $pdo->lastInsertId();
使用 MySQLi(MySQL Improved Extension)
MySQLi 专门用于 MySQL 数据库,支持面向对象和过程式两种风格。
面向对象风格
<?php
$mysqli = new mysqli('localhost', 'root', '', 'testdb');
if ($mysqli->connect_error) {
die('连接失败: ' . $mysqli->connect_error);
}
// 准备 SQL 语句
$sql = "SELECT id, name, email FROM users WHERE email = ? AND status = ?";
$stmt = $mysqli->prepare($sql);
// 绑定参数(s=string, i=integer, d=double, b=blob)
$email = 'user@example.com';
$status = 'active';
$stmt->bind_param("ss", $email, $status);
// 执行查询
$stmt->execute();
// 绑定结果变量(可选)
$stmt->bind_result($id, $name, $email_result);
// 获取结果
while ($stmt->fetch()) {
echo "ID: $id, Name: $name, Email: $email_result<br>";
}
// 关闭语句和连接
$stmt->close();
$mysqli->close();
?>
插入数据
$sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
$stmt = $mysqli->prepare($sql);
$name = '李四';
$email = 'lisi@example.com';
$age = 30;
$stmt->bind_param("ssi", $name, $email, $age);
if ($stmt->execute()) {
echo "插入成功,ID: " . $stmt->insert_id;
} else {
echo "错误: " . $stmt->error;
}
$stmt->close();
PDO vs MySQLi 对比
| 特性 | PDO | MySQLi |
|---|---|---|
| 数据库支持 | 12+ 种数据库 | 仅 MySQL |
| 命名占位符 | 支持 (name) |
仅问号 |
| 面向对象 | 是 | 是(也支持过程式) |
| 预处理语句 | 是 | 是 |
| 性能 | 类似 | 类似 |
| 推荐指数 |
最佳实践与注意事项
始终使用参数化查询
- ✅ 正确:
$stmt->execute(['user@example.com']) - ❌ 错误:
$pdo->query("SELECT * FROM users WHERE email='$email'")
不要手动转义(已过时)
- 不要使用
mysqli_real_escape_string(),参数化查询已经自动处理转义
处理批量插入(事务)
try {
$pdo->beginTransaction();
$sql = "INSERT INTO users (name, email) VALUES (:name, :email)";
$stmt = $pdo->prepare($sql);
foreach ($users as $user) {
$stmt->execute([
':name' => $user['name'],
':email' => $user['email']
]);
}
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo "失败: " . $e->getMessage();
}
使用 LIKE 查询(注意通配符处理)
$search = "%{$keyword}%"; // 通配符在参数中,SQL 模板不变
$sql = "SELECT * FROM products WHERE name LIKE :keyword";
$stmt = $pdo->prepare($sql);
$stmt->execute([':keyword' => $search]);
完整的 PDO 封装示例
<?php
class Database {
private static $instance = null;
private $pdo;
private function __construct() {
$config = [
'host' => 'localhost',
'dbname' => 'testdb',
'charset' => 'utf8mb4',
'username' => 'root',
'password' => ''
];
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
$this->pdo = new PDO($dsn, $config['username'], $config['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 关闭模拟预处理,使用真实的预处理
]);
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function query($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
public function fetchAll($sql, $params = []) {
return $this->query($sql, $params)->fetchAll();
}
public function fetchOne($sql, $params = []) {
return $this->query($sql, $params)->fetch();
}
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO $table ($columns) VALUES ($placeholders)";
$this->query($sql, $data);
return $this->pdo->lastInsertId();
}
public function update($table, $data, $where, $whereParams = []) {
$set = '';
foreach ($data as $key => $value) {
$set .= "$key = :$key, ";
}
$set = rtrim($set, ', ');
$sql = "UPDATE $table SET $set WHERE $where";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params)->rowCount();
}
}
// 使用示例
$db = Database::getInstance();
$users = $db->fetchAll("SELECT * FROM users WHERE age > :age", [':age' => 18]);
$user = $db->fetchOne("SELECT * FROM users WHERE id = :id", [':id' => 1]);
$newId = $db->insert('users', ['name' => '王五', 'email' => 'wangwu@example.com']);
?>
| 方法 | 推荐场景 |
|---|---|
| PDO | 通用、跨数据库、现代 PHP 项目首选 |
| MySQLi | 仅使用 MySQL、已有遗留项目 |
核心原则:永远不要直接将用户输入拼接到 SQL 语句中,始终使用参数化查询,这不仅防止 SQL 注入,还能提高查询性能和代码可读性。