PHP的PDO错误与错误处理
前言PDO 提供了三种错误处理策略用PDO::ATTR_ERRMODE属性切换。听起来是个小配置项实际上它是本章最容易踩坑的地方原因在于默认值变过。在 PHP 8.0.0 之前默认模式是PDO::ERRMODE_SILENT——出错了什么都不说只是把错误码放到一边等你自己去查。很多老代码正是靠这个沉默在跑。从 PHP 8.0.0 起手册明确规定默认模式改为PDO::ERRMODE_EXCEPTION。于是同一段代码从 PHP 7 升到 PHP 8 之后原本静默失败的查询会突然抛出异常把本来藏在水下的问题全掀到水面上。这既是一次升级事故的常见来源也是一次难得的纠错机会。第二个高频误解是把 PDO 异常当成普通异常处理。PDOException继承自RuntimeException但它的$code属性类型是int|string——PDO 会把 SQLSTATE 字符串例如42S02塞进code。所以if ($e-getCode() 1146)这种拿整数比对的写法永远不会成立。第三连接失败是特殊的。手册专门提示PDO::__construct()在连接失败时总是抛PDOException跟你设的PDO::ATTR_ERRMODE是什么无关。本文按 PHP 8.1 梳理三种错误模式的取舍、SQLSTATE 三元组的读法以及一套能直接用的统一错误处理写法。一、三种错误模式怎么选模式行为适用阶段PDO::ERRMODE_SILENT只设置错误码不抛不报PHP 8.0 之前的默认值现代代码不建议PDO::ERRMODE_WARNING设置错误码同时发出E_WARNING临时调试想让错误可见但不中断流程PDO::ERRMODE_EXCEPTION设置错误码并抛PDOExceptionPHP 8.0 起的默认值生产环境首选手册对ERRMODE_EXCEPTION的评价很实在它会在错误点把脚本炸掉从而非常快地指出问题位置而且因为脚本因异常终止时未提交的事务会被自动回滚一致性也有了兜底。此外它让你能集中写try/catch比在每一步后面判断返回值要少写大量嵌套。设置方式有两种推荐在构造函数里一次性给足?php // 适用于 PHP 8.0显式声明以免依赖版本默认值declare(strict_types1);$dsn mysql:host127.0.0.1;dbnameshop;charsetutf8mb4;try {$pdo new PDO($dsn, app, secret, [PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES false,]);} catch (PDOException $e) {// 连接失败一定会走到这里与 ERRMODE 无关error_log(DB connect failed: . $e-getMessage());http_response_code(500);exit(服务暂时不可用);}即使 PHP 8 的默认值已经是异常模式显式写出来仍然值得坚持。一是向下兼容时行为确定二是读代码的人一眼能看出这套代码的错误处理契约。有一点要特别提醒ERRMODE_WARNING在 Web 环境里并不好用。它发出的是 PHP 警告会混进页面输出如果display_errors开着警告内容会直接渲染到 HTML 里把表名、SQL 片段甚至连接信息暴露给访客。手册在连接章节专门警告过未捕获异常产生的致命错误回溯可能泄露连接细节所以生产服务器的display_errors应当设为0。二、SQLSTATE 与 errorInfo 三元组PDO 用 SQL-92 定义的 SQLSTATE 码作为统一错误标识由各驱动把自家错误码翻译过去。两个读取方法分工明确PDO::errorCode(): ?string—— 只返回一个五字符 SQLSTATE没有错误时返回null。PDO::errorInfo(): array—— 返回三元组信息更全。errorInfo()的数组结构是固定的下标内容0SQLSTATE 错误码五字符1驱动特定的错误码2驱动特定的错误信息PDOStatement上也有同名的errorCode()和errorInfo()语义一致但表示的是该语句上最近一次操作的错误。判断到底该看哪个对象有一个简单规则手册原文的意思错误来自语句对象上的调用就查那个语句对象错误来自数据库对象上的调用就在数据库对象上查。?php // 适用于 PHP 8.0演示 SILENT 模式下的手工检查$pdo new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE PDO::ERRMODE_SILENT]);$stmt $pdo-prepare(SELECT id FROM users WHERE email ?);if ($stmt false) {// prepare 失败错误记在数据库对象上var_dump($pdo-errorCode(), $pdo-errorInfo());exit(1);}if (!$stmt-execute([$email])) {// execute 失败错误记在语句对象上var_dump($stmt-errorCode(), $stmt-errorInfo());exit(1);}$rows $stmt-fetchAll();注意SELECT这类语句在 SILENT 模式下即使execute()成功也可能带回警告级状态所以严格来说还要检查errorCode()是否以01开头。这也是 SILENT 模式不推荐的原因——检查点太多漏一个就等于没有错误处理。三、PDOException 的两个特殊之处PDOException继承自RuntimeException手册在介绍里还加了一句态度鲜明的提醒不要在你自己的代码里抛出PDOException。它是 PDO 内部用来上报错误的类型语义上属于来自 PDO 层的信号。它的类摘要里有两条值得记住的信息class PDOException extends RuntimeException{protected int|string $code;public ?array $errorInfo null;}第一条$code是int|string。PDO 把 SQLSTATE 以字符串形式塞进code所以?php // 适用于 PHP 8.0try {$pdo-query(SELECT wrongcolumn FROM wrongtable);} catch (PDOException $e) {echo $e-getCode(); // 例如 42S02是字符串不是整数echo $e-getMessage(); // SQLSTATE[42S02]: Base table or view not found: ...}第二条PDOException上有errorInfo属性PHP 8.x 手册的类摘要中列出内容与PDO::errorInfo()对应。用它比去正则解析getMessage()里的SQLSTATE[...]前缀可靠得多?php // 适用于 PHP 8.0try {$pdo-prepare(INSERT INTO users (email) VALUES (?))-execute([$email]);} catch (PDOException $e) {$info $e-errorInfo ?? null; // 老环境上该属性可能不存在$sqlState $info[0] ?? (string) $e-getCode();if ($sqlState 23000) { // 完整性约束冲突// 例如邮箱唯一索引冲突转成友好的业务提示echo 该邮箱已被注册\n;} else {throw $e; // 其余错误继续向上抛}}这里用??取值是刻意的它既能处理errorInfo为null的情况也能在没有这个属性的旧版本上安全降级不会因为访问未定义属性而报警告。顺带提一下PDO::ERR_NONE这个常量。手册说它是个便利常量用来跟errorCode()的返回值做比较——因为errorCode()在没有错误时返回null拿它跟ERR_NONE比并不总是等价更稳妥的写法是直接判null或用 00000判断成功。四、一套可复用的错误处理骨架把上面这些东西合起来可以得到一个适合业务代码的封装底层统一抛异常顶层统一翻译。?php // 适用于 PHP 8.0declare(strict_types1);final class Db{private static ?PDO $pdo null;public static function conn(): PDO{if (self::$pdo instanceof PDO) {return self::$pdo;}try {self::$pdo new PDO(mysql:host127.0.0.1;dbnameshop;charsetutf8mb4,app,secret,[PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES false,]);} catch (PDOException $e) {error_log([db] connect: . $e-getMessage());throw new RuntimeException(数据库连接失败, 0, $e);}return self::$pdo;}/** SQLSTATE 到用户可读提示的映射只覆盖需要友好提示的几个 */private const FRIENDLY [23000 数据冲突请检查后重试,42S02 数据表不存在请联系管理员,HY000 数据库内部错误,];public static function run(string $sql, array $params []): PDOStatement{try {$stmt self::conn()-prepare($sql);$stmt-execute($params);return $stmt;} catch (PDOException $e) {$info $e-errorInfo ?? null;$sqlState $info[0] ?? (string) $e-getCode();// 详细错误只进日志绝不返回给调用方error_log(sprintf([db] %s | sql%s | params%s, $sqlState, $sql, json_encode($params)));$msg self::FRIENDLY[$sqlState] ?? 数据处理失败;throw new RuntimeException($msg, 0, $e);}}}这个骨架体现了三条原则都是从手册的行为约束里推出来的。其一原始异常一律不直接对外暴露。PDOException::getMessage()里带着 SQLSTATE、驱动错误码和表名直接返回给前端等于把数据库结构告诉攻击者。日志里留全量响应里只给翻译过的提示。其二连接异常单独处理。因为PDO::__construct()的异常不受ERRMODE控制把它和查询异常混在一条路径上容易误判。其三json_encode($params)记录参数时仍要留个心眼。如果参数里含密码、令牌日志本身就变成了泄露点生产环境应当对敏感字段做掩码。常见坑点依赖版本默认值❌ 什么都不设指望出错时会抛异常 ✅ 显式写PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTIONPHP 8.0 前默认是 SILENT用整数比对getCode()❌if ($e-getCode() 1146) { ... }✅$sqlState ($e-errorInfo ?? [])[0] ?? (string) $e-getCode();SQLSTATE 是字符串正则解析异常消息拿错误码❌preg_match(/SQLSTATE\[(\w)\]/, $e-getMessage(), $m);✅ 优先用errorInfo属性或PDO::errorInfo()三元组忘了连接异常不受 ERRMODE 影响❌ 设了ERRMODE_SILENT就以为new PDO()不会抛 ✅ 构造函数失败永远抛PDOException必须单独try/catch把errorInfo()当成当前错误❌ 随便找个 PDO 对象调errorInfo()就当拿到最近一次错误 ✅ 错误记在发起那次调用的对象上语句错查语句对象连接错查数据库对象生产环境把原始异常消息返回给前端❌echo $e-getMessage();✅ 日志记录全量响应只返回翻译后的提示SILENT 模式下只看execute()的返回值❌$stmt-execute($p);后不检查任何东西 ✅ 要么换用异常模式要么每次检查errorCode()并按 00000判断成功display_errors在生产开启❌ 未捕获异常导致的致命错误把回溯打到页面上 ✅ 生产display_errors0错误写日志手册明确提示回溯可能泄露连接细节总结项目内容默认错误模式PHP 8.0.0 起为PDO::ERRMODE_EXCEPTION之前为PDO::ERRMODE_SILENT三种模式ERRMODE_SILENT/ERRMODE_WARNING/ERRMODE_EXCEPTION连接异常PDO::__construct()失败总是抛PDOException与 ERRMODE 无关errorCode()返回?string即五字符 SQLSTATE无错时为nullerrorInfo()返回数组0为 SQLSTATE1为驱动错误码2为驱动错误消息PDOException::$code类型 intPDOException::$errorInfoPHP 8.x 中提供与PDO::errorInfo()对应PDO 的错误处理没有复杂机制难点全在细节约定上默认值在 PHP 8 变了、连接异常不受错误模式管、异常码是字符串而不是整数。把这三点记住然后坚持底层抛异常、顶层翻译、日志留全量、响应给口径的分层写法出错时既能定位问题又不会把数据库结构泄给外面。

相关新闻

Codex智能体实战:从代码生成到多场景自动化生产

Codex智能体实战:从代码生成到多场景自动化生产

我老早就想写这么一篇东西了。这两年“超级个体”这个概念被讲烂了,但真正能把一个人当一支团队用的,核心不是会多少工具,而是能不能把重复劳动交给系统去跑、自己只盯着关键节点。Codex这名字一出来,很多人以为它就是个“更强的编…

2026/10/9 17:10:53 阅读更多 →
代码生成器性能优化实战:从40秒到4秒的调优全解析

代码生成器性能优化实战:从40秒到4秒的调优全解析

我接手公司内部那个代码生成器的时候,它单次生成300张表的全套CRUD代码要跑40多秒。这个时长说慢不慢,但配合上每天几十次的生成操作,整个研发团队的耐心基本被磨没了。后来我花了两周时间做优化,把耗时压到了4秒以内。今天这篇就…

2026/10/9 17:10:53 阅读更多 →
UPC条形码校验码计算全解析:Excel公式与Bartender配置

UPC条形码校验码计算全解析:Excel公式与Bartender配置

1. 从一个超市扫码枪说起:UPC 条形码到底是怎么回事如果你在零售、仓储、电商或者制造业待过,大概率都跟条形码打过交道。收银台那一声“嘀”,背后其实就是 UPC 或者 EAN 这套编码体系在工作。UPC 全称 Universal Product Code,中…

2026/10/9 17:10:53 阅读更多 →

最新新闻

MSK调制解调仿真:Python实现误码率与功率谱分析

MSK调制解调仿真:Python实现误码率与功率谱分析

简介:MSK调制解调Matlab仿真代码包,面向通信工程专业学生与无线通信系统设计人员,涵盖信号生成、调制映射、相干解调及误码统计的完整仿真链路,可帮助快速理解最小移频键控的核心原理和性能评估方法。压缩包共6个文件,…

2026/10/9 17:48:08 阅读更多 →
为什么 openGym 放弃密码:WebAuthn Passkey 登录原理与安全设计解析

为什么 openGym 放弃密码:WebAuthn Passkey 登录原理与安全设计解析

为什么 openGym 放弃密码:WebAuthn Passkey 登录原理与安全设计解析 【免费下载链接】openGym https://github.com/DuarteSantos8/openGym 项目地址: https://gitcode.com/gh_mirrors/ope/openGym openGym 是一个自托管的健身房与体重追踪工具,它…

2026/10/9 17:48:08 阅读更多 →
MemoryAnalyzer 1.6.1实战:从堆转储到Java OOM根因定位

MemoryAnalyzer 1.6.1实战:从堆转储到Java OOM根因定位

简介:MemoryAnalyzer 1.6.1 的 Windows 版压缩包(2016 年 11 月 25 日构建)定位清晰,是面向 Java 开发者和运维人员的内存分析工具,用于读取堆转储文件,定位内存泄漏与对象异常占用。它由 Eclipse 基金会维…

2026/10/9 17:48:08 阅读更多 →
从合同审查到法律研究:智合AI等五款法律AI的场景对比

从合同审查到法律研究:智合AI等五款法律AI的场景对比

选法律AI,最怕的不是没得选,而是选错了方向。有人冲着名气下单,结果天天要用的场景里表现平平;有人图省事用免费版,碰上复杂案子又抓瞎。其实律师的日常就那几件事:查案例、审合同、写文书、做研究。把这几…

2026/10/9 17:48:08 阅读更多 →
模型推理服务平台是什么?AI落地的核心关键枢纽

模型推理服务平台是什么?AI落地的核心关键枢纽

很多人以为大模型的核心难点只在训练环节,其实真正决定AI能否落地商用、赋能业务的,是模型推理服务。模型训练是让算法学会能力,而推理服务是把训练好的模型转化为可调用、可复用、可服务业务的真实能力,是打通AI技术与产业应用的…

2026/10/9 17:48:07 阅读更多 →
后台管理静态模板:HTML+CSS+原生JS搭建与避坑指南

后台管理静态模板:HTML+CSS+原生JS搭建与避坑指南

简介:这是一套面向企业后台与网站管理系统开发者的中文版HTML后台管理静态模板,适合前端初学者、全栈开发者及需要快速搭建管理界面的项目团队使用。模板预置了登录、首页、品牌管理、产品列表、订单表单、权限控制、用户列表、广告管理、管理员设置等常…

2026/10/9 17:47:07 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →