PHP PDO:从基础到实战的数据库操作指南
在PHP开发中,数据库操作是核心环节之一。传统的MySQL扩展(如mysql_*函数)因安全隐患和功能局限逐渐被淘汰,而PDO(PHP Data Objects)凭借其强大的跨数据库支持、预处理防注入特性和统一接口设计,成为现代PHP项目的首选数据库操作工具。本文将从基础配置到实战应用,全面解析PDO的核心功能与最佳实践。
一、PDO基础:为何选择PDO?
PDO是PHP官方提供的数据库访问抽象层,支持MySQL、PostgreSQL、SQLite等多种数据库,通过统一的接口屏蔽不同数据库的差异,同时提供更安全、高效的操作方式。相比传统扩展,PDO的核心优势包括:
- 预处理语句防SQL注入:通过参数化查询避免恶意SQL注入攻击
- 错误处理机制:支持异常捕获和错误模式配置,便于调试与生产环境监控
- 事务支持:完整的事务管理,确保数据一致性
- 跨数据库兼容性:同一套代码可无缝切换不同数据库(如MySQL→PostgreSQL)
- 面向对象设计:以对象方式操作数据库,代码更清晰易维护
二、PDO安装与基础配置
1. 环境检查与安装
PDO扩展默认随PHP安装,可通过phpinfo()查看是否启用。若未启用,需在php.ini中取消注释extension=pdo_mysql(MySQL扩展)或对应数据库扩展(如pdo_pgsql),重启服务器后生效。
2. 连接数据库的核心步骤
PDO连接数据库需通过new PDO()实例化,关键参数包括DSN(数据源名称)、用户名、密码及连接选项。以下是MySQL连接示例:
// 设置数据库连接参数
$host = 'localhost';
$dbname = 'test_db';
$username = 'root';
$password = 'password';
try {
// 构建DSN:mysql:host=主机;dbname=数据库名;charset=字符集
$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
// 创建PDO实例,设置错误模式为异常
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 异常模式
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC // 默认获取关联数组
]);
echo "数据库连接成功";
} catch (PDOException $e) {
// 捕获连接错误
die("连接失败:" . $e->getMessage());
}
关键说明:
- DSN格式因数据库而异(如PostgreSQL为
pgsql:host=...,SQLite为sqlite:/path/to/db) charset=utf8mb4需显式设置,避免中文乱码ERRMODE_EXCEPTION模式下,连接错误或操作错误会抛出异常,便于统一捕获处理
三、PDO预处理语句:安全操作数据库的核心
预处理语句是PDO最核心的安全特性,通过将SQL模板与参数分离,避免SQL注入风险。
1. 基础使用:命名占位符 vs 问号占位符
PDO支持两种参数绑定方式:命名占位符(:name)和问号占位符(?),后者更简洁,前者可读性更强。
// 示例:查询用户信息(命名占位符)
$sql = "SELECT * FROM users WHERE username = :username AND status = :status";
$stmt = $pdo->prepare($sql); // 预处理SQL
$stmt->execute([ // 绑定参数
':username' => 'test_user',
':status' => 1
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC); // 获取单条结果
// 示例:插入数据(问号占位符)
$sql = "INSERT INTO users (username, email, created_at) VALUES (?, ?, NOW())";
$stmt = $pdo->prepare($sql);
$stmt->execute(['new_user', 'user@example.com']); // 绑定参数
echo "新用户ID:" . $pdo->lastInsertId(); // 获取最后插入ID
2. 结果集处理:高效获取数据
预处理语句执行后,可通过fetch()系列方法获取结果:
fetch():获取单条记录,支持参数(如PDO::FETCH_ASSOC返回关联数组)fetchAll():获取所有记录,返回二维数组fetchColumn():获取指定列的单一值
// 获取所有用户(关联数组)
$sql = "SELECT id, username FROM users";
$stmt = $pdo->query($sql); // 简化写法(无需预处理)
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 遍历结果
foreach ($users as $user) {
echo $user['username'] . "\n";
}
四、错误处理:保障系统稳定性
PDO通过setAttribute()或构造函数参数设置错误模式,常见模式包括:
PDO::ERRMODE_SILENT:仅返回错误码,不抛出异常(需手动检查)PDO::ERRMODE_WARNING:触发PHP警告(开发调试用)PDO::ERRMODE_EXCEPTION:抛出异常(生产环境推荐)
// 设置全局错误模式(生产环境建议)
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 事务中捕获异常示例
try {
$pdo->beginTransaction();
// 执行多条SQL
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$pdo->commit(); // 成功提交事务
} catch (PDOException $e) {
$pdo->rollBack(); // 失败回滚事务
echo "操作失败:" . $e->getMessage();
}
五、事务管理:确保数据一致性
事务通过beginTransaction()开启,commit()提交,rollBack()回滚,适用于多表关联操作(如转账、订单创建)。
// 示例:转账事务
$pdo->beginTransaction();
try {
// 扣减转出账户余额
$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE id = :from");
$stmt1->execute([':amount' => 500, ':from' => 1]);
// 增加转入账户余额
$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE id = :to");
$stmt2->execute([':amount' => 500, ':to' => 2]);
$pdo->commit(); // 全部成功则提交
echo "转账成功";
} catch (PDOException $e) {
$pdo->rollBack(); // 任一操作失败则回滚
echo "转账失败:" . $e->getMessage();
}
六、实战案例:用户注册与登录系统
以下是一个完整的PDO应用示例,包含数据库连接、用户注册、登录验证及事务处理:
<?php
// 数据库配置
$config = [
'host' => 'localhost',
'dbname' => 'user_auth',
'user' => 'root',
'pass' => 'password'
];
// 注册用户函数
function registerUser($username, $email, $password) {
global $config;
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
// 检查用户名是否存在
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = :username");
$stmt->execute([':username' => $username]);
if ($stmt->rowCount() > 0) {
return ['status' => 'error', 'msg' => '用户名已存在'];
}
// 加密密码(生产环境建议用password_hash+password_verify
