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 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | 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');