PHP中的官方操作数据库PDO
PDO(PHP Data Objects)是 PHP 官方推荐的数据库抽象层 ,它支持 MySQL、PostgreSQL、SQLite 等多种数据库,提供了预处理语句 (防 SQL 注入)、事务处理等高级特性,面向对象风格,代码简洁易维护,是现代 PHP 开发的首选。
开启 PDO 扩展
在使用 PDO 前,需确认 PHP 环境已开启 pdo_mysql 扩展。创建 phpinfo.php 文件:
php
<?php
phpinfo();
?>
访问页面,搜索 pdo_mysql,若看到 PDO Driver for MySQL 为 enabled,则说明已开启。
若未开启,修改 php.ini,取消 extension=pdo_mysql(Windows)或 extension=pdo_mysql.so(Linux)前的注释,重启 Web 服务即可。
连接 MySQL 数据库
PDO 通过DSN(数据源名称) 连接数据库,连接时建议设置 错误模式为异常 、字符集为 utf8mb4,这是生产环境的标准配置。
php
new PDO(
string $dsn, // DSN 字符串(包含主机、数据库名、字符集)
string $username, // 数据库用户名
string $password, // 数据库密码
array $options = [] // 可选配置数组(错误模式、属性等)
): PDO
标准连接
php
<?php
// 数据库配置
$host = 'localhost';
$dbname = 'test_db';
$username = 'root';
$password = 'your_password';
$charset = 'utf8mb4'; // 支持 emoji,推荐
// DSN 字符串
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
// 配置选项:设置错误模式为异常,设置默认获取方式为关联数组
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 异常模式(推荐,方便调试)
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 默认返回关联数组
PDO::ATTR_EMULATE_PREPARES => false, // 禁用模拟预处理,使用真实预处理(更安全)
];
try {
// 连接数据库
$pdo = new PDO($dsn, $username, $password, $options);
echo "数据库连接成功!";
} catch (PDOException $e) {
// 捕获连接异常
die("数据库连接失败:" . $e->getMessage());
}
?>
预处理语句(防 SQL 注入)
SQL 注入是最常见的安全漏洞,PDO 的 预处理语句 是解决这个问题的最佳方案。
它的原理是:先发送 SQL 模板(带占位符),再发送参数,数据与逻辑完全分离,绝对安全。
两种占位符
PDO 支持两种占位符,推荐使用命名占位符(更直观):
- 命名占位符 :
:name(如:username、:age) - 问号占位符 :
?(按顺序绑定参数)
使用预处理查询数据(SELECT)
php
<?php
// 假设已连接数据库,$pdo 是 PDO 对象
// 1. 准备 SQL 模板(使用命名占位符)
$sql = "SELECT id, username, email FROM users WHERE age > :age AND city = :city";
// 2. 预处理 SQL
$stmt = $pdo->prepare($sql);
// 3. 绑定参数并执行(方式一:直接传数组给 execute,推荐)
$params = [
':age' => 18,
':city' => '北京'
];
$stmt->execute($params);
// 4. 获取结果
// fetch():获取一条数据
// fetchAll():获取所有数据
$users = $stmt->fetchAll();
// 遍历结果
foreach ($users as $user) {
echo "ID: " . $user['id'] . " - 用户名: " . $user['username'] . "<br>";
}
?>
使用预处理插入数据(INSERT)
php
<?php
// 假设已连接数据库
// 1. 准备 SQL
$sql = "INSERT INTO users (username, email, age) VALUES (:username, :email, :age)";
$stmt = $pdo->prepare($sql);
// 2. 绑定参数并执行
$params = [
':username' => '张三',
':email' => 'zhangsan@example.com',
':age' => 25
];
$stmt->execute($params);
// 3. 获取插入的自增 ID(非常常用!)
$newId = $pdo->lastInsertId();
echo "数据插入成功,新记录 ID:" . $newId;
?>
使用预处理更新 / 删除数据(UPDATE/DELETE)
php
<?php
// 假设已连接数据库
// 更新数据
$sql = "UPDATE users SET age = :age WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':age' => 26,
':id' => 1
]);
// 获取受影响的行数
$affectedRows = $stmt->rowCount();
echo "更新成功,受影响行数:" . $affectedRows;
// 删除数据同理
$sql = "DELETE FROM users WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([':id' => 10]);
echo "删除成功,受影响行数:" . $stmt->rowCount();
?>
获取结果的常用方式
PDO 提供了多种获取结果的方式,通过 fetch() 或 fetchAll() 的参数控制:
| 模式常量 | 说明 |
|---|---|
PDO::FETCH_ASSOC |
返回关联数组(键名为字段名) |
PDO::FETCH_NUM |
返回索引数组(键名为数字索引) |
PDO::FETCH_OBJ |
返回对象(属性名为字段名) |
PDO::FETCH_COLUMN |
返回指定列的值(配合 fetchColumn()) |
php
<?php
// 假设已连接数据库
$sql = "SELECT id, username FROM users LIMIT 2";
$stmt = $pdo->prepare($sql);
$stmt->execute();
// 1. 关联数组(默认,推荐)
$usersAssoc = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "关联数组:<pre>";
print_r($usersAssoc);
echo "</pre>";
// 2. 对象
$stmt->execute(); // 重新执行,重置指针
$usersObj = $stmt->fetchAll(PDO::FETCH_OBJ);
echo "对象:<pre>";
print_r($usersObj);
echo "</pre>";
// 3. 获取单个值(比如获取用户总数)
$sql = "SELECT COUNT(*) FROM users";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$count = $stmt->fetchColumn(); // 获取第一列的值
echo "用户总数:" . $count;
?>
事务处理(保证数据一致性)
事务用于确保一组 SQL 操作要么全部成功,要么全部失败(例如:转账操作,A 扣钱和 B 加钱必须同时成功)。PDO 的事务操作非常简洁。
| 方法 | 说明 |
|---|---|
beginTransaction() |
开启事务(关闭自动提交) |
commit() |
提交事务(所有操作生效) |
rollBack() |
回滚事务(所有操作撤销) |
php
<?php
// 假设已连接数据库
try {
// 1. 开启事务
$pdo->beginTransaction();
// 2. 执行操作 1:A 账户扣 100 元
$sql = "UPDATE accounts SET balance = balance - 100 WHERE id = 1";
$stmt = $pdo->prepare($sql);
$stmt->execute();
if ($stmt->rowCount() === 0) {
throw new Exception("A 账户扣款失败");
}
// 3. 执行操作 2:B 账户加 100 元
$sql = "UPDATE accounts SET balance = balance + 100 WHERE id = 2";
$stmt = $pdo->prepare($sql);
$stmt->execute();
if ($stmt->rowCount() === 0) {
throw new Exception("B 账户加款失败");
}
// 4. 提交事务
$pdo->commit();
echo "转账成功!";
} catch (Exception $e) {
// 5. 回滚事务(如果任何一步出错)
$pdo->rollBack();
echo "转账失败:" . $e->getMessage();
}
?>
错误处理:三种模式
PDO 提供了三种错误处理模式,推荐使用异常模式 (PDO::ERRMODE_EXCEPTION),因为它能清晰地抛出错误,方便调试和处理。
| 模式常量 | 说明 |
|---|---|
PDO::ERRMODE_SILENT |
静默模式(默认,不报错,只设置错误码) |
PDO::ERRMODE_WARNING |
警告模式(抛出 E_WARNING 警告) |
PDO::ERRMODE_EXCEPTION |
异常模式(抛出 PDOException 异常,推荐) |
最佳实践总结
- 永远使用预处理语句:这是防 SQL 注入的底线,不要拼接 SQL 字符串。
- 设置字符集为 utf8mb4:支持 emoji,避免中文乱码。
- 使用异常模式 :
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,方便调试。 - 禁用模拟预处理 :
PDO::ATTR_EMULATE_PREPARES => false,使用 MySQL 原生预处理,更安全。 - 合理使用
fetchAll():如果数据量很大(比如几万条),不要用fetchAll(),避免内存溢出,应使用fetch()逐行处理。 - 事务处理关键操作:涉及多步数据变更(如转账、订单)时,必须使用事务保证一致性。