PHP中的官方操作数据库PDO

1 阅读3分钟

PHP中的官方操作数据库PDO

PDO(PHP Data Objects)是 PHP 官方推荐的数据库抽象层,它支持 MySQL、PostgreSQL、SQLite 等多种数据库,提供了预处理语句(防 SQL 注入)、事务处理等高级特性,面向对象风格,代码简洁易维护,是现代 PHP 开发的首选。

开启 PDO 扩展

在使用 PDO 前,需确认 PHP 环境已开启 pdo_mysql 扩展。创建 phpinfo.php 文件:

<?php
phpinfo();
?>

访问页面,搜索 pdo_mysql,若看到 PDO Driver for MySQLenabled,则说明已开启。

若未开启,修改 php.ini,取消 extension=pdo_mysql(Windows)或 extension=pdo_mysql.so(Linux)前的注释,重启 Web 服务即可。

连接 MySQL 数据库

PDO 通过DSN(数据源名称) 连接数据库,连接时建议设置 错误模式为异常字符集为 utf8mb4,这是生产环境的标准配置。

new PDO(
    string $dsn,           // DSN 字符串(包含主机、数据库名、字符集)
    string $username,      // 数据库用户名
    string $password,      // 数据库密码
    array $options = []    // 可选配置数组(错误模式、属性等)
): PDO

标准连接

<?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 支持两种占位符,推荐使用命名占位符(更直观):

  1. 命名占位符:name(如 :username:age
  2. 问号占位符?(按顺序绑定参数)

使用预处理查询数据(SELECT)

<?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
// 假设已连接数据库

// 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
// 假设已连接数据库

// 更新数据
$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
// 假设已连接数据库
$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
// 假设已连接数据库

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 异常,推荐)

最佳实践总结

  1. 永远使用预处理语句:这是防 SQL 注入的底线,不要拼接 SQL 字符串。
  2. 设置字符集为 utf8mb4:支持 emoji,避免中文乱码。
  3. 使用异常模式PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,方便调试。
  4. 禁用模拟预处理PDO::ATTR_EMULATE_PREPARES => false,使用 MySQL 原生预处理,更安全。
  5. 合理使用 fetchAll() :如果数据量很大(比如几万条),不要用 fetchAll(),避免内存溢出,应使用 fetch() 逐行处理。
  6. 事务处理关键操作:涉及多步数据变更(如转账、订单)时,必须使用事务保证一致性。