Skip to content

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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  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');

参考链接