mysql菜鸟教程

专栏导航

18.3 使用PHP连接MySQL:PDO

     在PHP中,有多种方式连接MySQL数据库,例如 mysqli 扩展和 PDO(PHP Data Objects)。其中,PDO 提供了一个统一的轻量级接口,使得无论你使用哪种数据库(MySQL、PostgreSQL、SQLite等),代码风格都保持一致,同时它支持预处理语句、事务等高级特性,是现代PHP开发中推荐的方式。

一、什么是PDO?

PDO(PHP Data Objects)是一个数据库访问抽象层。它并不直接提供数据库操作的方法,而是定义了一套统一的接口,由各个数据库的驱动去实现。因此,使用PDO可以轻松切换数据库后端,只需更改连接字符串和少量配置,而无需重写大量代码。

PDO的主要优点:

  • 数据库无关性:代码可移植性强。

  • 预处理语句:有效防止SQL注入。

  • 事务支持:便于保证数据一致性。

  • 异常处理:可通过异常捕获错误,更符合现代编程习惯。

二、启用PDO扩展

通常情况下,PHP默认已启用PDO扩展和MySQL驱动。你可以通过创建 phpinfo() 页面查看是否已启用 pdo_mysql 扩展。若未启用,需要在 php.ini 中取消以下行的注释(或添加):

extension=pdo_mysql

然后重启Web服务器。

三、建立数据库连接

使用PDO连接MySQL需要创建一个 PDO 类的实例,构造函数参数包括:

  • DSN(数据源名称):包含数据库类型、主机、端口、数据库名等信息。

  • 用户名:数据库用户名。

  • 密码:数据库密码。

  • 选项:可选,一个数组,用于配置PDO行为。

连接示例

<?php
$dsn = 'mysql:host=localhost;port=3306;dbname=testdb;charset=utf8mb4';
$username = 'root';
$password = 'your_password';
$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());
}

解释

  • DSN中的 charset=utf8mb4 指定字符集,支持emoji等。

  • 选项 ERRMODE_EXCEPTION 使PDO在错误时抛出异常,便于集中处理。

  • DEFAULT_FETCH_MODE 设置默认获取结果集的模式,常见的有 FETCH_ASSOC(关联数组)、FETCH_NUM(索引数组)、FETCH_OBJ(对象)。

  • EMULATE_PREPARES 设为 false 可以确保使用MySQL原生的预处理,提高安全性。

四、执行查询并获取结果

1. 执行查询(SELECT)

使用 query() 方法执行SQL语句,返回一个 PDOStatement 对象,然后通过 fetch() 或 fetchAll() 获取结果。

<?php
$sql = "SELECT id, name, email, age FROM users";
$stmt = $pdo->query($sql);

// 获取所有行,返回关联数组(因为设置了默认FETCH_MODE)
$users = $stmt->fetchAll();

foreach ($users as $user) {
    echo $user['id'] . ': ' . $user['name'] . ' - ' . $user['email'] . '<br>';
}

如果只需要一行,可以使用 fetch()

$stmt = $pdo->query("SELECT * FROM users WHERE id = 1");
$user = $stmt->fetch(); // 返回一行,或 false

2. 执行修改操作(INSERT、UPDATE、DELETE)

使用 exec() 执行无结果集返回的SQL,返回受影响的行数。

<?php
$sql = "DELETE FROM users WHERE age < 18";
$affectedRows = $pdo->exec($sql);
echo "删除了 " . $affectedRows . " 行。";

但 exec() 不应对用户输入使用,因为它不支持参数绑定,存在SQL注入风险。安全的做法是使用预处理语句。

五、预处理语句(Prepared Statements)

预处理语句是防止SQL注入的最有效手段。它将SQL模板和参数分开处理,确保参数不会被解释为SQL代码。

使用预处理执行INSERT

<?php
$sql = "INSERT INTO users (name, email, age) VALUES (:name, :email, :age)";
$stmt = $pdo->prepare($sql);

$stmt->execute([
    ':name' => '李四',
    ':email' => 'lisi@example.com',
    ':age' => 22
]);

echo "插入成功,新ID:" . $pdo->lastInsertId();

使用位置占位符(?)

$sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['王五', 'wangwu@example.com', 25]);

执行SELECT并绑定参数

$sql = "SELECT * FROM users WHERE age > :age";
$stmt = $pdo->prepare($sql);
$stmt->execute([':age' => 18]);
$users = $stmt->fetchAll();

绑定参数并指定类型

可以使用 bindValue() 或 bindParam() 更精细地控制参数类型。

$stmt = $pdo->prepare("SELECT * FROM users WHERE age > :age");
$stmt->bindValue(':age', 18, PDO::PARAM_INT);
$stmt->execute();

六、事务处理

当需要执行多条语句,并确保它们全部成功或全部失败时,可以使用事务。

<?php
try {
    $pdo->beginTransaction(); // 开启事务

    $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1");
    $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE user_id = 2");

    $pdo->commit(); // 提交事务
    echo "转账成功!";
} catch (Exception $e) {
    $pdo->rollBack(); // 回滚事务
    echo "转账失败:" . $e->getMessage();
}

七、错误处理模式

PDO支持三种错误模式:

  • PDO::ERRMODE_SILENT:默认模式,不主动报错,需通过 errorCode() 和 errorInfo() 检查。

  • PDO::ERRMODE_WARNING:产生警告,不影响代码执行。

  • PDO::ERRMODE_EXCEPTION:抛出异常,推荐使用,便于统一捕获。

通过 setAttribute() 或构造函数选项设置。

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

八、完整示例:简单的用户管理

下面是一个包含增删改查的完整示例,演示如何使用PDO操作MySQL。

<?php
// config.php - 数据库配置
$config = [
    'dsn' => 'mysql:host=localhost;dbname=testdb;charset=utf8mb4',
    'username' => 'root',
    'password' => 'your_password',
    'options' => [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]
];

// 创建PDO连接
try {
    $pdo = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
} catch (PDOException $e) {
    die("数据库连接失败:" . $e->getMessage());
}

// 查询所有用户
function getUsers($pdo) {
    $stmt = $pdo->query("SELECT id, name, email, age FROM users");
    return $stmt->fetchAll();
}

// 根据ID查询用户
function getUserById($pdo, $id) {
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

// 添加用户
function addUser($pdo, $name, $email, $age) {
    $sql = "INSERT INTO users (name, email, age) VALUES (:name, :email, :age)";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute([':name' => $name, ':email' => $email, ':age' => $age]);
}

// 更新用户
function updateUser($pdo, $id, $name, $email, $age) {
    $sql = "UPDATE users SET name = :name, email = :email, age = :age WHERE id = :id";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute([':id' => $id, ':name' => $name, ':email' => $email, ':age' => $age]);
}

// 删除用户
function deleteUser($pdo, $id) {
    $stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
    return $stmt->execute([$id]);
}

// 示例使用
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 处理表单提交,此处省略表单验证
    $name = $_POST['name'];
    $email = $_POST['email'];
    $age = $_POST['age'];
    addUser($pdo, $name, $email, $age);
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

if (isset($_GET['delete'])) {
    deleteUser($pdo, $_GET['delete']);
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

?>
<!DOCTYPE html>
<html>
<head>
    <title>用户管理</title>
</head>
<body>
    <h1>用户列表</h1>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>邮箱</th>
            <th>年龄</th>
            <th>操作</th>
        </tr>
        <?php foreach (getUsers($pdo) as $user): ?>
        <tr>
            <td><?= $user['id'] ?></td>
            <td><?= htmlspecialchars($user['name']) ?></td>
            <td><?= htmlspecialchars($user['email']) ?></td>
            <td><?= $user['age'] ?></td>
            <td>
                <a href="?delete=<?= $user['id'] ?>" onclick="return confirm('确定删除?')">删除</a>
            </td>
        </tr>
        <?php endforeach; ?>
    </table>

    <h2>添加用户</h2>
    <form method="post">
        <label>姓名: <input type="text" name="name" required></label><br>
        <label>邮箱: <input type="email" name="email" required></label><br>
        <label>年龄: <input type="number" name="age" required></label><br>
        <input type="submit" value="添加">
    </form>
</body>
</html>

九、注意事项

  • 永远不要相信用户输入:始终使用预处理语句 + 参数绑定。

  • 选择合适的获取模式:根据需求选择关联数组、对象等。

  • 关闭连接:PDO对象销毁时自动关闭连接,通常无需手动关闭,但在长生命周期脚本中可设为 null 以释放资源。

  • 处理异常:使用 try-catch 捕获 PDOException 并合理处理错误。

  • 字符集:在DSN中指定 charset=utf8mb4,避免中文乱码。

小结

  • PDO 提供了一个统一的数据库访问接口,支持多种数据库。

  • 使用 PDO 的预处理语句可以有效防止SQL注入。

  • 通过 setAttribute 可以设置错误模式和默认获取方式。

  • 事务处理使多个操作保持原子性。

  • 掌握PDO是PHP现代开发的基础技能之一。


所有评论

关于我 备案号:蜀ICP备2023042032号-1