pdo 默认不抛异常,必须设 pdo::attr_errmode 为 pdo::errmode_exception;捕获异常时应使用 $e->getcode()、$e->errorinfo[1] 和 $e->errorinfo[2];prepare() 不报错,execute() 才可能抛异常;事务中需 intransaction() 检查后 rollback()。

PDO 默认不抛异常,PDO::ATTR_ERRMODE 必须手动设为 PDO::ERRMODE_EXCEPTION
PHP 的 PDO 默认用静默模式处理错误:出错只返回 false,不报错也不抛异常,很容易漏掉 SQL 执行失败。这不是 bug,是设计如此——但绝大多数现代应用需要明确失败信号。
必须在创建实例后立刻设置错误模式,否则后续所有操作都沿用默认值:
$pdo = new PDO($dsn, $user, $pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- 没设这行,
$pdo->query("SELECT * FROM nonexistent")只返回false,不触发catch -
PDO::ERRMODE_SILENT和PDO::ERRMODE_WARNING基本不用,前者难调试,后者混入 PHP 警告,无法捕获 - 这个设置不能写在
new PDO()的第四个参数(选项数组)里?可以,但得写全:['PDO::ATTR_ERRMODE' => PDO::ERRMODE_EXCEPTION]—— 注意键名不是常量,是字符串
捕获 PDOException 时,别只打印 $e->getMessage()
PDOException 继承自 Exception,但自带数据库层关键信息。只输出 getMessage() 会丢掉错误码、SQL 状态码和原始 SQL,线上排障基本抓瞎。
真正有用的字段是这三个:
立即学习“PHP免费学习笔记(深入)”;
-
$e->getCode():返回的是 SQLSTATE(如42S02),不是 MySQL 错误号;想拿 MySQL 原生错误号,用$e->errorInfo[1] -
$e->errorInfo:数组,结构为[SQLSTATE, driver code, driver message],第三项才是人类可读的错误描述 -
$e->getTraceAsString()配合日志用,但别直接暴露给前端
示例写法:
本书将PHP开发与MySQL应用相结合,分别对PHP和MySQL做了深入浅出的分析,不仅介绍PHP和MySQL的一般概念,而且对PHP和MySQL的Web应用做了较全面的阐述,并包括几个经典且实用的例子。 本书是第4版,经过了全面的更新、重写和扩展,包括PHP5.3最新改进的特性(例如,更好的错误和异常处理),MySQL的存储过程和存储引擎,Ajax技术与Web2.0以及Web应用需要注意的安全
try {
$pdo->query("DROP TABLE users");
} catch (PDOException $e) {
error_log(sprintf(
"PDO error %s (%s): %s, SQL: %s",
$e->getCode(),
$e->errorInfo[1],
$e->errorInfo[2],
$e->getMessage()
));
}预处理语句出错时,异常可能发生在 execute() 而非 prepare()
很多人以为 prepare() 失败才抛异常,其实不然。MySQL 在 prepare() 阶段只校验语法,真正校验表/列是否存在、权限是否足够,是在 execute() 时才做。
这意味着:
-
$stmt = $pdo->prepare("SELECT id FROM nonexistent");成功返回PDOStatement对象 - 但
$stmt->execute()会抛PDOException,错误码可能是42S02(表不存在) - 所以异常捕获必须包住
execute(),或整个prepare() → execute()流程 - 如果用了
bindValue()或bindParam(),类型不匹配(比如把字符串绑定到整型字段)也可能在execute()报错,而非绑定时
事务中发生异常,记得检查 inTransaction() 再 rollback()
事务没手动回滚,异常后连接还挂着未提交状态,下次请求可能复用这个连接,导致数据不一致或锁等待超时。
安全做法是:不管 commit() 成不成功,只要进了事务,异常后必须 rollback(),且先确认是否真在事务中:
try {
$pdo->beginTransaction();
$pdo->exec("INSERT INTO logs ...");
$pdo->exec("UPDATE accounts ...");
$pdo->commit();
} catch (PDOException $e) {
if ($pdo->inTransaction()) {
$pdo->rollback();
}
throw $e;
}- 不查
inTransaction()就调rollback(),会触发新警告:There is no active transaction - 某些驱动(如 SQLite)在 commit 失败时自动 rollback,但 MySQL 不会,行为不统一,必须显式处理
- 别依赖析构函数或 finally 自动清理——PDO 连接复用时,事务状态跨请求残留是真实风险
最麻烦的其实是嵌套事务和保存点,PDO 本身不支持真正的嵌套,靠 SAVEPOINT 模拟的话,异常路径里的保存点释放逻辑很容易漏,得单独管理状态。










