PHP PDO 事务异常时如何避免自动提交半成品
订单保存经常不是一条 SQL:先写订单主表,再写明细、扣减库存,最后记录操作日志。只要中间一步失败,前面的写入就不能留在数据库里。PHP PDO 的正确做法是把这些语句放进显式事务,成功路径只在最后调用 commit(),异常路径调用 rollBack(),不要依赖“某条 SQL 报错后数据库会自动替你收尾”。
- 连接后启用
PDO::ERRMODE_EXCEPTION,让失败可被同一个catch捕获。 beginTransaction()到commit()是成功边界,任何异常都应进入回滚分支。- 事务能否真正回滚还取决于驱动、表引擎和是否执行了会隐式提交的 DDL。
先把“半成品”变成一个可回滚单元
PDO 新建连接时通常处于自动提交模式,每条写语句可能拥有自己的隐式事务。需要原子性时,必须显式调用 beginTransaction()。下面用一个订单和库存的简化场景说明边界;示例中的表名、数量和异常都是演示数据,不代表已经在本机执行。
PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$pdo->beginTransaction();
try {
// 主表和明细必须处在同一个事务里,避免只生成订单头。
$order = $pdo->prepare(
'INSERT INTO orders (user_id, total_amount) VALUES (:user_id, :total)'
);
$order->execute(['user_id' => 42, 'total' => 199.00]);
$orderId = (int) $pdo->lastInsertId();
// 库存不足或 SQL 失败时,异常会跳到 catch,不会提前提交订单。
$stock = $pdo->prepare(
'UPDATE inventory SET quantity = quantity - :qty
WHERE sku = :sku AND quantity >= :qty'
);
$stock->execute(['qty' => 1, 'sku' => 'KB-100']);
if ($stock->rowCount() !== 1) {
throw new RuntimeException('库存不足,取消订单');
}
// 只有全部写操作成功,才把半成品变成正式数据。
$pdo->commit();
} catch (Throwable $error) {
// 事务可能已被数据库或驱动终止,先判断活动状态再回滚。
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
// 生产环境记录内部错误,返回给用户的消息应保持简洁。
throw $error;
}
?>

这里把 beginTransaction() 放在 try 外,是为了让“启动事务失败”和“事务内 SQL 失败”分开理解。实际项目也可以把它放进更大的 try,但捕获后不要无条件回滚:没有活动事务时,rollBack() 本身可能再次抛出异常。
异常模式解决的是“能不能回滚”,不是业务判断
PDO::ERRMODE_EXCEPTION 让执行失败抛出 PDOException,从而不会因为忘记检查 execute() 返回值而继续走到提交。它并不会替你判断库存是否足够、金额是否正确,也不会把业务规则失败自动变成异常,所以示例中仍然主动抛出 RuntimeException。
写入代码建议保持一个清晰的节奏:准备参数、执行语句、检查业务结果;所有成功条件满足后才提交。不要在每一步后分别调用 commit(),否则后续失败只能回滚后半段,主表和明细就会失去原子性。
三个容易让回滚失效的边界
| 边界 | 表现 | 处理方式 |
|---|---|---|
| 表引擎或驱动不支持事务 | 方法看似成功,数据却没有按预期回滚 | 确认 PDO 驱动和数据库表引擎;MySQL 写表通常使用 InnoDB |
| 事务外写入 | 某条 SQL 在 beginTransaction() 之前执行 | 把所有相关写操作移到事务入口之后 |
| 事务内执行 DDL | CREATE TABLE、DROP TABLE 等可能触发隐式 COMMIT | 把结构变更和业务写入拆开,避免把 DDL 当作可回滚业务步骤 |
PDO 只能在驱动层判断是否支持事务,不能保证当前数据库条件一定具备回滚能力;例如 MySQL 的非事务表可能让 beginTransaction() 返回成功,但写入仍无法像 InnoDB 那样撤销。另一个常见误区是把“脚本结束自动回滚”当成业务设计。它只能作为异常退出时的安全兜底,成功路径仍应显式提交,异常路径仍应显式回滚和记录。

上线前用三组场景确认边界
- 成功场景:订单、明细和库存都满足条件,检查三张相关表都留下对应记录。
- 异常场景:在库存更新后人为抛出异常,检查订单和明细没有残留,库存也没有被扣减。
- 重复提交场景:故意再次调用
commit()或重复进入事务,确认应用能记录错误,而不是静默产生第二份数据。
测试时不要只看 PHP 日志里的异常。还要在数据库连接断开后查询结果,并确认连接复用前已经结束事务。若使用多个数据库连接,PDO 事务只约束当前连接,不能自然覆盖另一个连接上的写操作。
常见问题
PDO 抛出异常后还需要手动 rollBack() 吗?
需要。异常模式负责把控制流送进 catch,不会替你完成业务层清理;在确认 inTransaction() 为真后显式回滚更清楚。
只调用 rollBack() 能撤销事务开始前的 INSERT 吗?
不能。回滚只作用于当前 beginTransaction() 之后、提交之前的操作,事务开始前的写入已经按自动提交处理。
为什么 MySQL 回滚了但数据仍在?
先检查表是否使用支持事务的引擎,再检查中间是否执行了会隐式提交的 DDL;最后确认所有语句使用的是同一个 PDO 连接。
Go build -ldflags 传入空字符串为什么被截断
- 上一篇
- Go build -ldflags 传入空字符串为什么被截断
- 下一篇
- 运营团队用Lovart做多渠道活动素材,哪些环节仍要人工把关?
-
- 文章 · php教程 | 3小时前 | session · php教程 · 会话安全 · php SessionHandlerInterface session.use_strict_mode validateId
- PHP session.use_strict_mode 如何与自定义 session handler 配合
- 269浏览 收藏
-
- 文章 · php教程 | 6小时前 | PHP · PHP 8.5 · 数组函数 · php 空数组 array_last array_first
- PHP array_first 和 array_last 遇到空数组返回什么
- 367浏览 收藏
-
- 文章 · php教程 | 8小时前 |
- PHP 8.5 pipe operator 适合怎样串联纯函数
- 490浏览 收藏
-
- 文章 · php教程 | 1天前 | 依赖管理 · PHP · composer · 自动加载 · php Composer autoload-dev require-dev
- PHP Composer autoload-dev 为何在线环境找不到类
- 460浏览 收藏
-
- 文章 · php教程 | 1天前 | 数据结构 · php教程 · SplFixedArray · 数组对比 · PHP实战 · php 数据结构 PHP数组 SplFixedArray 数组性能
- PHP SPLFixedArray 和普通数组有什么取舍
- 281浏览 收藏
-
- 文章 · php教程 | 1天前 | PHP · curl · 并发请求 · curl_multi_exec curl_multi_select curl_multi_info_read
- PHP curl_multi_exec 如何处理多个并发请求
- 371浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 104次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 21次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 31次使用
-
- AGI-Eval
- AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
- 22次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 259次使用
-
- golang实现PHP数组特性的方法
- 2023-02-16 371浏览
-
- PHP与Go语言之间的通信详解
- 2023-01-07 347浏览
-
- php和go语言的区别有哪些
- 2023-03-04 112浏览
-
- Go 数据库事务如何处理提交后副作用:Outbox、重试边界与幂等消费
- 2026-08-25 238浏览
-
- HTTP 的 response 中的响应体和头部是分开发送的吗?
- 2023-01-28 387浏览

