PDO 错误模式
概述
PDO 提供三种错误处理模式:PDO::ERRMODE_SILENT(静默)、PDO::ERRMODE_WARNING(警告)和 PDO::ERRMODE_EXCEPTION(异常)。推荐使用异常模式,因为它不会静默忽略错误,且可以通过 try-catch 优雅处理。
适用场景
- 所有数据库操作
- 错误日志记录
- 用户友好错误提示
- 调试与监控
基础概念
三种错误模式
| 模式 | 常量 | 行为 |
|---|---|---|
| 静默 | PDO::ERRMODE_SILENT | 不报错,需手动检查 errorCode() |
| 警告 | PDO::ERRMODE_WARNING | 触发 E_WARNING |
| 异常 | PDO::ERRMODE_EXCEPTION | 抛出 PDOException(推荐) |
错误信息获取
| 方法 | 功能 |
|---|---|
errorCode() | SQLSTATE 错误码(5 字符) |
errorInfo() | 详细错误信息数组 |
PDOException | 异常对象 |
默认模式
PDO::ERRMODE_SILENT 是默认值。如果不显式设置,所有错误都会被静默忽略!
语法与代码示例
异常模式(推荐)
php
<?php
declare(strict_types=1);
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
try {
$stmt = $pdo->prepare('SELECT * FROM nonexistent_table');
$stmt->execute();
} catch (PDOException $e) {
echo "SQLSTATE: {$e->getCode()}\n";
echo "Message: {$e->getMessage()}\n";
echo "File: {$e->getFile()}:{$e->getLine()}\n";
echo "Trace: {$e->getTraceAsString()}\n";
}静默模式
php
<?php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
]);
$stmt = $pdo->query('SELECT * FROM nonexistent_table');
// 错误不会自动报告
if ($stmt === false) {
$code = $pdo->errorCode();
$info = $pdo->errorInfo();
echo "SQLSTATE: {$code}\n";
echo "错误码: {$info[1]}\n";
echo "错误信息: {$info[2]}\n";
}
// PDOStatement 的错误
$stmt = $pdo->prepare('INVALID SQL');
if ($stmt === false) {
print_r($pdo->errorInfo());
}警告模式
php
<?php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING,
]);
// 错误会触发 E_WARNING,但不会停止执行
$stmt = $pdo->query('SELECT * FROM nonexistent_table');
// Warning: PDO::query(): SQLSTATE[42S02]: Base table or view not found
if ($stmt === false) {
echo "查询失败\n";
}设置/获取错误模式
php
<?php
// 设置错误模式
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 获取当前错误模式
$mode = $pdo->getAttribute(PDO::ATTR_ERRMODE);
echo match ($mode) {
PDO::ERRMODE_SILENT => 'SILENT',
PDO::ERRMODE_WARNING => 'WARNING',
PDO::ERRMODE_EXCEPTION => 'EXCEPTION',
default => 'UNKNOWN',
};实战示例
错误处理中间件
php
<?php
declare(strict_types=1);
class DatabaseErrorHandler
{
/**
* 处理 PDO 异常
*/
public function handle(PDOException $e, bool $log = true): void
{
$sqlState = $e->getCode();
$message = $e->getMessage();
if ($log) {
error_log("[DB Error] SQLSTATE: {$sqlState}, Message: {$message}");
}
// 根据错误类型处理
if (str_contains($sqlState, '23000')) {
// 唯一约束冲突
throw new DuplicateEntryException($message);
}
if (str_contains($sqlState, 'HY000')) {
// 连接错误
throw new DatabaseConnectionException($message);
}
throw new DatabaseException($message, (int)$e->getCode(), $e);
}
/**
* 格式化错误信息
*/
public function formatError(PDOException $e): array
{
return [
'sqlstate' => $e->getCode(),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
];
}
}
class DatabaseException extends RuntimeException {}
class DuplicateEntryException extends DatabaseException {}
class DatabaseConnectionException extends DatabaseException {}错误码映射
php
<?php
declare(strict_types=1);
// 常见 SQLSTATE 错误码
const SQLSTATE_ERRORS = [
'23000' => '约束冲突(唯一键/外键)',
'42S02' => '表或视图不存在',
'42S22' => '列不存在',
'22003' => '数值超出范围',
'08001' => '无法连接数据库',
'HY000' => '一般错误',
'IM001' => '驱动不支持此功能',
];
function getSqlStateMessage(string $code): string
{
return SQLSTATE_ERRORS[$code] ?? "未知错误 ({$code})";
}注意事项
PDOException 的 code 是 SQLSTATE
php
<?php
try {
$pdo->query('INVALID');
} catch (PDOException $e) {
echo $e->getCode(); // "42S22" 或 "00000" 等字符串
// 注意:不是整数!
}连接失败的错误
php
<?php
// 连接失败时,PDOException 的构造方式不同
try {
$pdo = new PDO('mysql:host=nonexistent', 'root', 'pass');
} catch (PDOException $e) {
// 错误码可能是字符串或整数
echo "Code: {$e->getCode()}\n";
echo "Message: {$e->getMessage()}\n";
}最佳实践
1. 始终使用异常模式
php
<?php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 必须!
]);2. 分层处理异常
php
<?php
try {
$stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
$stmt->execute(['email' => $email]);
} catch (PDOException $e) {
if (str_contains($e->getCode(), '23000')) {
// 唯一约束冲突
echo "邮箱已存在\n";
} else {
// 记录日志
error_log($e->getMessage());
echo "系统错误,请稍后重试\n";
}
}进阶用法
调试与测试技巧
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');