Skip to content

PDO 查询方法

概述

PDO 提供了多种查询执行和数据获取方法。query() 执行一次性查询,exec() 执行无返回结果的 SQL,prepare() + execute() 执行预处理语句。fetch()fetchAll()fetchColumn()fetchObject() 获取不同格式的结果。

适用场景

  • 简单查询用 query()
  • 修改操作用 exec()
  • 安全查询用 prepare()
  • 不同格式用不同的 fetch 方法

基础概念

查询方法对比

方法功能返回值
query()执行 SQL 并返回结果集PDOStatement|false
exec()执行 SQL(无结果集)int|false
prepare()准备预处理语句PDOStatement|false
execute()执行预处理语句bool

获取方法对比

方法功能返回值
fetch()获取一行mixed|false
fetchAll()获取所有行array|false
fetchColumn()获取一列值mixed|false
fetchObject()获取为对象object|false

语法与代码示例

query() 执行查询

php
<?php

// 不带参数的一次性查询
$users = $pdo->query('SELECT * FROM users')->fetchAll();

// 注意:query() 中的用户输入不安全!
// $pdo->query("SELECT * FROM users WHERE id = {$_GET['id']}"); // SQL 注入!

// 安全做法:使用 prepare

exec() 执行修改

php
<?php

// exec 返回受影响的行数
$affected = $pdo->exec("DELETE FROM logs WHERE created_at < '2024-01-01'");
echo "删除了 {$affected} 行\n";

// 创建表
$pdo->exec("CREATE TABLE IF NOT EXISTS test (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

fetch 获取模式

php
<?php

$stmt = $pdo->query('SELECT id, name, email FROM users');

// PDO::FETCH_ASSOC - 关联数组
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo "ID: {$row['id']}, Name: {$row['name']}\n";
}

// PDO::FETCH_NUM - 数字索引数组
while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
    echo "ID: {$row[0]}, Name: {$row[1]}\n";
}

// PDO::FETCH_BOTH - 同时包含关联和数字索引(默认)
$row = $stmt->fetch(PDO::FETCH_BOTH);

// PDO::FETCH_OBJ - 匿名对象
while ($user = $stmt->fetch(PDO::FETCH_OBJ)) {
    echo "ID: {$user->id}, Name: {$user->name}\n";
}

// PDO::FETCH_CLASS - 映射到类
class UserDTO {
    public function __construct(public int $id = 0, public string $name = '') {}
}
$users = $pdo->query('SELECT id, name FROM users')
    ->fetchAll(PDO::FETCH_CLASS, UserDTO::class);

// PDO::FETCH_COLUMN - 获取单列
$ids = $pdo->query('SELECT id FROM users')->fetchAll(PDO::FETCH_COLUMN);
// [1, 2, 3, ...]

// PDO::FETCH_KEY_PAIR - 键值对
$map = $pdo->query('SELECT id, name FROM users')
    ->fetchAll(PDO::FETCH_KEY_PAIR);
// [1 => 'Alice', 2 => 'Bob', ...]

fetchAll 获取所有行

php
<?php

// fetchAll
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE status = ?');
$stmt->execute(['active']);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 获取特定列
$names = $stmt->fetchAll(PDO::FETCH_COLUMN, 1); // 第二列
// ['Alice', 'Bob', 'Charlie']

// 分组获取
$groups = $stmt->fetchAll(PDO::FETCH_GROUP);
// 第一列作为键

fetchColumn 获取单值

php
<?php

// 获取单个值
$count = $pdo->query('SELECT COUNT(*) FROM users')->fetchColumn();
echo "用户总数: {$count}\n";

// 预处理方式
$stmt = $pdo->prepare('SELECT COUNT(*) FROM users WHERE status = ?');
$stmt->execute(['active']);
$activeCount = $stmt->fetchColumn();

rowCount

php
<?php

// rowCount 返回受影响的行数(SELECT 在某些驱动中返回 0)
$stmt = $pdo->prepare('UPDATE users SET status = ? WHERE id = ?');
$stmt->execute(['inactive', 123]);
echo "受影响行数: " . $stmt->rowCount() . "\n";

// DELETE
$stmt = $pdo->prepare('DELETE FROM logs WHERE created_at < ?');
$stmt->execute(['2024-01-01']);
echo "删除行数: " . $stmt->rowCount() . "\n";

实战示例

分页查询

php
<?php

declare(strict_types=1);

class Paginator
{
    public static function paginate(PDO $pdo, string $sql, array $params, int $page, int $perPage): array
    {
        $offset = ($page - 1) * $perPage;

        $countSql = preg_replace('/SELECT.*?FROM/', 'SELECT COUNT(*) FROM', $sql);
        $stmt = $pdo->prepare($countSql);
        $stmt->execute($params);
        $total = (int)$stmt->fetchColumn();

        $paginatedSql = $sql . " LIMIT {$perPage} OFFSET {$offset}";
        $stmt = $pdo->prepare($paginatedSql);
        $stmt->execute($params);
        $items = $stmt->fetchAll(PDO::FETCH_ASSOC);

        return [
            'items' => $items,
            'total' => $total,
            'page' => $page,
            'per_page' => $perPage,
            'total_pages' => (int)ceil($total / $perPage),
        ];
    }
}

简单的 Repository 模式

php
<?php

declare(strict_types=1);

class UserRepository
{
    public function __construct(private PDO $pdo) {}

    public function findById(int $id): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        return $user ?: null;
    }

    public function findByEmail(string $email): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE email = :email');
        $stmt->execute(['email' => $email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        return $user ?: null;
    }

    public function findAllActive(): array
    {
        return $this->pdo->query('SELECT * FROM users WHERE status = "active"')
            ->fetchAll(PDO::FETCH_ASSOC);
    }

    public function count(): int
    {
        return (int)$this->pdo->query('SELECT COUNT(*) FROM users')->fetchColumn();
    }

    public function create(string $name, string $email): int
    {
        $stmt = $this->pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
        $stmt->execute(['name' => $name, 'email' => $email]);
        return (int)$this->pdo->lastInsertId();
    }

    public function update(int $id, array $data): bool
    {
        $set = implode(', ', array_map(fn($k) => "{$k} = :{$k}", array_keys($data)));
        $data['id'] = $id;
        $stmt = $this->pdo->prepare("UPDATE users SET {$set} WHERE id = :id");
        return $stmt->execute($data) > 0;
    }

    public function delete(int $id): bool
    {
        $stmt = $this->pdo->prepare('DELETE FROM users WHERE id = :id');
        return $stmt->execute(['id' => $id]) > 0;
    }
}

注意事项

fetch() 返回 false

php
<?php

// fetch() 在没有更多行时返回 false
$row = $stmt->fetch();
if ($row === false) {
    echo "没有数据\n";
}

// fetchAll() 在无数据时返回空数组
$rows = $stmt->fetchAll();
// 即使没有数据也是 [](不是 false)

SELECT 的 rowCount

php
<?php

// PDO 的 rowCount 对 SELECT 语句的行为因驱动而异
// MySQL PDO 驱动支持 SELECT rowCount
// 其他驱动可能返回 0

// 如果需要获取 SELECT 行数,先 fetchAll 再 count
$allRows = $stmt->fetchAll();
$count = count($allRows);

最佳实践

1. 设置默认获取模式

php
<?php

// 设置默认获取模式为关联数组
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

2. 使用 fetch 遍历大结果集

php
<?php

// 对于大结果集,逐行获取比 fetchAll 更省内存
$stmt = $pdo->query('SELECT * FROM large_table');
while ($row = $stmt->fetch()) {
    // 处理每行
}

进阶用法

调试与测试技巧

php
<?php
declare(strict_types=1);

// 单元测试辅助函数
function createTestResource(): mixed
{
    return match (true) {
        default => new stdClass(),
    };
}

// 调试输出函数
function debugOutput(mixed , string  = ''): void
{
     =  ? ": " : '';
     .= print_r(, true);
    fwrite(STDERR,  . "\n");
}

// 性能基准测试
function benchmark(callable , int  = 1000): float
{
     = hrtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $fn();
    }
    return (hrtime(true) - $start) / 1e9;
}

日志记录实践

php
<?php
declare(strict_types=1);

/**
 * 简易日志记录器
 */
class SimpleLogger
{
    private string $logFile;
    private string $level = 'INFO';

    public function __construct(string $logFile)
    {
        $this->logFile = $logFile;
    }

    public function info(string $message, array $context = []): void
    {
        $this->log('INFO', $message, $context);
    }

    public function warning(string $message, array $context = []): void
    {
        $this->log('WARNING', $message, $context);
    }

    public function error(string $message, array $context = []): void
    {
        $this->log('ERROR', $message, $context);
    }

    private function log(string $level, string $message, array $context): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
        $line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

配置与环境检测

php
<?php
declare(strict_types=1);

// 环境检测工具
class EnvironmentChecker
{
    public static function checkRequirements(array $requirements): array
    {
        $results = [];
        foreach ($requirements as $name => $check) {
            $results[$name] = is_callable($check) ? $check() : false;
        }
        return $results;
    }

    public static function getSystemInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'sapi' => PHP_SAPI,
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
            'loaded_extensions' => get_loaded_extensions(),
        ];
    }
}

常见问题排查

问题可能原因解决方案
连接超时网络问题/配置错误检查配置,增加超时时间
权限不足文件/目录权限使用 chmod/chown 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?php
declare(strict_types=1);

// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
    if (version_compare(PHP_VERSION, $minVersion, '<')) {
        throw new RuntimeException(
            sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
        );
    }
}

ensureVersion('8.1.0');

参考链接