Skip to content

PDO 连接与配置

概述

PDO(PHP Data Objects)是 PHP 的通用数据库访问层,提供统一的接口访问多种数据库。PDO 通过 DSN(数据源名称)字符串连接数据库,支持丰富的配置属性(PDO::ATTR_*)。使用 PDO 可以让应用在不同数据库之间轻松切换。

适用场景

  • 多数据库类型支持
  • 安全的预处理语句
  • 事务管理
  • 数据库抽象层

基础概念

DSN 格式

php
<?php

// MySQL DSN
$dsn = 'mysql:host=localhost;port=3306;dbname=myapp;charset=utf8mb4';

// PostgreSQL DSN
$dsn = 'pgsql:host=localhost;port=5432;dbname=myapp';

// SQLite DSN
$dsn = 'sqlite:/path/to/database.sqlite';

// SQL Server DSN
$dsn = 'sqlsrv:Server=localhost;Database=myapp';

常用 PDO 属性

属性说明推荐值
PDO::ATTR_ERRMODE错误模式PDO::ERRMODE_EXCEPTION
PDO::ATTR_DEFAULT_FETCH_MODE默认获取模式PDO::FETCH_ASSOC
PDO::ATTR_EMULATE_PREPARES模拟预处理false
PDO::ATTR_PERSISTENT持久连接false
PDO::MYSQL_ATTR_INIT_COMMAND连接后执行的命令SET NAMES utf8mb4

PHP 8.4+ 新特性

PHP 8.4 引入了 PDO::connect()PDO::getDriver() 和驱动特定子类,提供了更现代的连接管理方式。

语法与代码示例

基本 PDO 连接

php
<?php

declare(strict_types=1);

$dsn = 'mysql:host=localhost;port=3306;dbname=myapp;charset=utf8mb4';
$username = 'root';
$password = 'secret';

try {
    $pdo = new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);

    echo "连接成功\n";
} catch (PDOException $e) {
    die("连接失败: {$e->getMessage()}\n");
}

连接管理类

php
<?php

declare(strict_types=1);

class Database
{
    private static ?PDO $instance = null;

    public static function getInstance(): PDO
    {
        if (self::$instance === null) {
            self::$instance = self::createConnection();
        }
        return self::$instance;
    }

    private static function createConnection(): PDO
    {
        $config = [
            'host'     => getenv('DB_HOST') ?: 'localhost',
            'port'     => (int)(getenv('DB_PORT') ?: 3306),
            'database' => getenv('DB_DATABASE') ?: 'myapp',
            'username' => getenv('DB_USERNAME') ?: 'root',
            'password' => getenv('DB_PASSWORD') ?: '',
            'charset'  => 'utf8mb4',
        ];

        $dsn = sprintf(
            'mysql:host=%s;port=%d;dbname=%s;charset=%s',
            $config['host'],
            $config['port'],
            $config['database'],
            $config['charset']
        );

        $pdo = new PDO($dsn, $config['username'], $config['password'], [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
            PDO::ATTR_PERSISTENT         => false,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
        ]);

        return $pdo;
    }

    /**
     * 断开连接
     */
    public static function disconnect(): void
    {
        self::$instance = null;
    }

    private function __construct() {}
    private function __clone() {}
}

多数据库连接

php
<?php

declare(strict_types=1);

class ConnectionManager
{
    private array $connections = [];

    public function addConnection(string $name, string $dsn, string $username, string $password, array $options = []): void
    {
        $defaults = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ];

        $this->connections[$name] = new PDO(
            $dsn,
            $username,
            $password,
            array_merge($defaults, $options)
        );
    }

    public function connection(string $name = 'default'): PDO
    {
        if (!isset($this->connections[$name])) {
            throw new RuntimeException("连接 '{$name}' 不存在");
        }
        return $this->connections[$name];
    }
}

// 使用
$manager = new ConnectionManager();
$manager->addConnection('mysql', 'mysql:host=localhost;dbname=app', 'root', 'pass');
$manager->addConnection('sqlite', 'sqlite:/tmp/app.db', '', '');

$mysql = $manager->connection('mysql');
$sqlite = $manager->connection('sqlite');

PHP 8.4+ PDO::connect()

php
<?php

// PHP 8.4+ 新的连接方式
$pdo = PDO::connect('mysql:host=localhost;dbname=myapp', 'root', 'pass');

// 获取驱动名称
$driver = $pdo->getDriver(); // 'mysql'

// 使用驱动特定子类
class MySQLConnection extends PDO
{
    // MySQL 特定的便捷方法
    public function lastInsertId(): string
    {
        return parent::lastInsertId();
    }
}

实战示例

健康检查连接

php
<?php

declare(strict_types=1);

class DatabaseHealthCheck
{
    public static function check(PDO $pdo): array
    {
        $info = [];

        try {
            // 测试连接
            $pdo->query('SELECT 1');
            $info['status'] = 'ok';

            // 获取服务器信息
            $info['server_version'] = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
            $info['client_version'] = $pdo->getAttribute(PDO::ATTR_CLIENT_VERSION);
            $info['connection_status'] = $pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS);
            $info['driver_name'] = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);

            // 获取数据库大小(MySQL)
            $stmt = $pdo->query(
                "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
                 FROM information_schema.tables WHERE table_schema = DATABASE()"
            );
            $info['database_size_mb'] = $stmt->fetchColumn() ?: 0;

        } catch (PDOException $e) {
            $info['status'] = 'error';
            $info['error'] = $e->getMessage();
        }

        return $info;
    }
}

// 使用
$pdo = Database::getInstance();
$health = DatabaseHealthCheck::check($pdo);
print_r($health);

注意事项

连接超时配置

php
<?php

// 设置连接超时(MySQL 特定属性)
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
    PDO::ATTR_TIMEOUT => 5,  // 连接超时 5 秒
]);

连接泄漏

php
<?php

// 不要在循环中创建连接
// 不好
foreach ($items as $item) {
    $pdo = new PDO($dsn, $user, $pass);
    $pdo->query("INSERT ...");
    // 连接没有关闭!
}

// 好:复用连接
$pdo = new PDO($dsn, $user, $pass);
foreach ($items as $item) {
    $pdo->query("INSERT ...");
}

最佳实践

1. 配置从环境变量读取

php
<?php

function createPdo(): PDO
{
    return new PDO(
        sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4',
            getenv('DB_HOST'), getenv('DB_NAME')
        ),
        getenv('DB_USER'),
        getenv('DB_PASS')
    );
}

2. 始终设置 ERRMODE_EXCEPTION

php
<?php

// 好:异常模式,错误不会被忽略
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

// 不好:静默模式,错误被忽略
$pdo = new PDO($dsn, $user, $pass); // PDO::ERRMODE_SILENT(默认)

3. 关闭 EMULATE_PREPARES

php
<?php

// EMULATE_PREPARES = false 使用真正的预处理语句
// 更安全、更高效(MySQL)
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

进阶用法

调试与测试技巧

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

参考链接