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 注入!
// 安全做法:使用 prepareexec() 执行修改
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 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 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');