连接失败时先检查pdo_mysql扩展、dsn中charset=utf8mb4及用户名密码是否正确传入构造函数;必须设pdo::attr_errmode=>pdo::errmode_exception以捕获异常;多库需独立实例,预处理是防注入底线。

连接失败时先看这三件事
连不上不是代码写错了,大概率是环境或配置卡在第一步。遇到 PDOException 报错,别急着改 SQL,先确认:是否启用了 pdo_mysql 扩展、DSN 里有没有漏写 charset=utf8mb4、用户名密码是不是传进了构造函数而不是拼进 DSN。
常见错误现象:
-
Class 'PDO' not found→ PHP 编译时没启用 PDO 核心扩展 -
could not find driver→pdo_mysql没开启(php -m | grep pdo看不到它) -
SQLSTATE[HY000] [1045] Access denied→ 用户名密码错,或 MySQL 用户没授权该 host
DSN 必须严格按格式写:mysql:host=localhost;dbname=testdb;charset=utf8mb4。不要加 port=3306,除非你真改了端口;charset=utf8mb4 必须写在 DSN 里,靠 SET NAMES 或建表时设编码救不了连接层的乱码。
为什么必须设 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
默认的 SILENT 模式会让错误静默失败——query() 返回 false,prepare() 返回 null,但你不 catch 就根本不知道哪条语句挂了。线上出问题时只能靠猜。
设成异常模式后,所有数据库操作失败都会抛 PDOException,你可以统一用 try/catch 捕获,记录日志、返回友好提示,而不是让脚本崩在半路。
顺手加上这两项能省不少事:
-
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC:后续fetch()默认返回关联数组,不用每次写fetch(PDO::FETCH_ASSOC) -
PDO::ATTR_EMULATE_PREPARES => false:关掉模拟预处理,让 MySQL 服务端真正校验 SQL 语法和类型,避免 INT 字段被当成字符串传入
多数据库连接不能共用一个 $pdo 实例
想同时查 MySQL 和 PostgreSQL?别想着“切换 database”——PDO 的单个连接对象只属于一个数据库驱动和一个服务器实例。跨库操作分两种情况:
- 同一 MySQL 服务器下的不同库:用
db1.users和db2.orders这种写法,一个$pdo就够 - MySQL + PostgreSQL 或不同服务器:必须创建两个独立实例,比如
$pdo_mysql和$pdo_pgsql,各自管理连接、事务、错误处理
推荐用配置数组 + 工厂函数封装,避免硬编码:
$configs = [
'mysql' => ['dsn' => 'mysql:host=localhost;dbname=shop;charset=utf8mb4', 'user' => 'root', 'pass' => ''],
'pgsql' => ['dsn' => 'pgsql:host=localhost;dbname=report', 'user' => 'rep_user', 'pass' => 'xxx']
];
<p>function getPdoConnection(string $key): PDO {
global $configs;
if (!isset($configs[$key])) {
throw new InvalidArgumentException("Unknown DB config: $key");
}
$cfg = $configs[$key];
$pdo = new PDO($cfg['dsn'], $cfg['user'], $cfg['pass']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}</p>
调用:$db1 = getPdoConnection('mysql');、$db2 = getPdoConnection('pgsql');
预处理不是可选项,是必选项
哪怕只是查一个固定 ID:$pdo->query("SELECT * FROM user WHERE id = " . $_GET['id']) —— 这种写法在真实项目里等于裸奔。PDO 的 prepare() + execute() 不是性能优化技巧,是防注入的底线。
正确姿势:
- 用户输入、URL 参数、表单值,一律走占位符:
WHERE status = ?或WHERE name = :name - 表名、字段名、ORDER BY 子句不能参数化,必须白名单校验:
in_array($table, ['users', 'posts'], true) - LIKE 查询别把 % 拼进参数:
WHERE title LIKE CONCAT('%', ?, '%'),? 本身只传关键词
批量插入也一样:用 INSERT INTO t (a,b) VALUES (?, ?), (?, ?) 配合循环 execute(),比逐条 query() 快且安全。1000 行以上建议分批,每次 200 行以内。
真正容易被忽略的是:连接成功 ≠ 数据库一直可用。网络抖动、超时、权限变更都可能让下一条 execute() 失败。不能只靠 new PDO 是否抛异常来判断“连得上”,得每条关键查询都做好异常兜底。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











