当前位置:首页 > 文章列表 > 文章 > php教程 > PHP PDO 事务完整工作流:开启事务、提交、回滚和异常模式

PHP PDO 事务完整工作流:开启事务、提交、回滚和异常模式

来源:17golang原创 2026-06-16 15:58:16 0浏览 收藏

PHP 项目里只要一次操作会改多张表,就很容易遇到“前面写入成功,后面失败,数据只保存了一半”的问题。比如创建订单时,先写订单主表,再扣库存,再写订单明细。如果中间任何一步失败,最好让前面的修改一起撤回,这就是事务要解决的核心问题。

这篇文章不只讲一个函数怎么调用,而是把 PDO 事务整理成一套可复用工作流:什么时候开启事务、每个阶段检查什么、失败后怎么回滚、最后如何确认数据一致。按这个流程写,代码会更稳,也更容易排查线上数据不一致问题。

摘要

PDO 事务的推荐流程是:连接后先设置错误模式,进入业务写入前开启事务,多条写入都成功后提交;任意一步失败时进入异常处理,确认仍在事务中就回滚。最后不要只相信接口返回成功,还要检查主表、明细表和库存变化是否一致。

适合人群

  • 正在用 PHP 和 PDO 操作 MySQL 的后端开发者。
  • 需要处理订单、库存、余额、积分、日志等多表写入场景的项目维护者。
  • 遇到过接口报错后数据库只保存一部分数据,想系统整理事务写法的读者。
目录
  • 目标和边界:事务要保证什么
  • 全流程总览:一次 PDO 事务从开启到结束
  • 阶段一:连接后先设置异常模式
  • 阶段二:把多条写入包进同一个事务
  • 阶段三:失败时回滚并保留错误信号
  • 我的推荐流程
  • 容易踩坑的地方
  • 落地速查表

目标和边界:事务要保证什么

先把边界定清楚:事务不是让 SQL 永远不报错,也不是自动帮我们修复业务数据。它解决的是一组数据库修改的“一起成功,或者一起撤回”。因此,适合放进同一个事务里的操作,通常有明确的业务一致性要求。

场景 是否适合事务 原因
创建订单和写订单明细 适合 主表和明细必须同时存在
扣库存和写扣减记录 适合 库存变化需要有对应记录
写数据库后调用外部接口 谨慎 外部接口通常不能跟数据库一起回滚
只查一条列表数据 通常不需要 没有多步写入一致性问题

本文示例假设数据库表使用支持事务的存储引擎,比如 MySQL InnoDB。还要注意,事务应该尽量短,业务校验要放在合适位置,避免把耗时网络请求、文件处理、复杂计算长时间放在事务中。

全流程总览:一次 PDO 事务从开启到结束

完整流程可以压缩成五个动作:接收 PHP 请求,开启事务,写主表,写明细,最后提交;如果中间任何一步失败,就走回滚路径。重点不是把代码包进 try 里就结束,而是每个阶段都要有明确检查点。

PHP PDO 事务从 PHP 请求、开启事务、写主表、写明细到提交或回滚的流程图

到这一步先记住一个判断标准:只要后续失败会让前面写入失去意义,就应该考虑放在同一个事务中。比如订单主表已经插入,但明细插入失败,这个订单对用户来说就是不完整的。

阶段一:连接后先设置异常模式

第一阶段的目标,是让数据库错误不要静默过去。PDO 默认错误处理方式如果配置不合适,某条 SQL 失败后可能只是返回错误状态,后面的提交逻辑仍然继续走。这样最容易出现“看起来成功,实际数据不完整”。

推荐在创建连接后立刻设置错误模式:

$dsn = 'mysql:host=127.0.0.1;dbname=demo;charset=utf8mb4';
$pdo = new PDO($dsn, 'demo_user', 'demo_pass');

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

这一阶段的检查点很简单:写一条确定会失败的测试 SQL,确认代码能进入错误处理分支。不要在生产环境随意测试,可以在本地测试库或单元测试里验证。

阶段二:把多条写入包进同一个事务

第二阶段的目标,是让同一组业务写入共享一个事务边界。下面用“创建订单、扣库存、写订单明细”做示例。为了突出事务流程,这里用 query() 演示,实际项目中参数来源于用户输入时,应改用参数绑定方案。

try {
    $pdo->beginTransaction();

    $pdo->query(\"INSERT INTO orders (user_id, total_amount, status)
        VALUES (1001, 19900, 'pending')\");

    $orderId = $pdo->lastInsertId();

    $pdo->query(\"UPDATE products
        SET stock = stock - 1
        WHERE id = 7 AND stock > 0\");

    $changedRows = $pdo->query(\"SELECT ROW_COUNT()\")->fetchColumn();
    if ((int) $changedRows !== 1) {
        throw new RuntimeException('库存不足,订单不能继续创建');
    }

    $pdo->query(\"INSERT INTO order_items (order_id, product_id, quantity, price)
        VALUES ({$orderId}, 7, 1, 19900)\");

    $pdo->commit();
} catch (Throwable $error) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    throw $error;
}

这一阶段的关键动作有三个:在第一条写入前开启事务,在所有写入成功后提交,在业务条件不满足时主动抛出错误信号。库存扣减就是典型检查点,不能只看 SQL 没报错,还要看影响行数是否符合预期。

阶段三:失败时回滚并保留错误信号

第三阶段的目标,是失败后既要撤回数据库修改,也要让上层知道这次操作没有成功。不要在错误处理里只写回滚,然后直接返回“成功”。那样前端和调用方会误以为订单创建完成。

PHP PDO 事务的异常模式、业务校验、失败回滚、数量确认和结果一致检查图

建议错误处理逻辑保持三步:

  1. 先判断 inTransaction(),仍在事务中再回滚。
  2. 记录必要的错误上下文,比如用户 ID、商品 ID、订单临时信息。
  3. 把错误继续交给上层处理,让接口返回失败状态。

为什么要判断 inTransaction()?因为某些失败可能发生在事务开启前,或者提交之后。直接调用回滚可能带来新的错误。先判断当前状态,可以让错误处理更稳。

我的推荐流程

把上面的内容落成项目规范,可以按下面顺序执行:

步骤 目标 关键动作 检查点
1 连接可靠 设置错误模式 数据库错误会进入错误处理分支
2 边界清晰 第一条写入前开启事务 事务里只放需要一致的写入
3 业务可控 每个关键写入后做业务校验 影响行数、余额、库存符合预期
4 失败可撤回 错误分支判断状态后回滚 失败后主表和明细没有残留半截数据
5 结果可验证 提交后查关键数据 主表、明细、库存记录一致

如果这套流程要封装成公共方法,建议封装的是“事务边界”和“错误处理”,不要把具体业务 SQL 硬塞进通用函数。业务校验往往和订单、库存、余额规则强相关,过度封装反而会让排查更难。

容易踩坑的地方

1. 只开启事务,没有打开异常模式

这会让部分失败不够明显。事务外壳看起来存在,但错误没有及时打断流程,最后仍可能提交不完整数据。连接创建后先设置错误模式,是事务代码的基础动作。

2. 事务里夹杂耗时外部操作

事务应该尽量短。如果在事务中调用远程接口、上传文件、生成复杂报表,数据库连接和锁可能被占用太久。更稳的方式是先完成必要数据库修改,提交后再通过任务队列处理后续动作。

3. 回滚后吞掉错误

回滚只是把数据库改动撤回,不代表业务已经成功。错误处理分支要让上层知道失败原因,否则调用方可能继续走“支付成功”“发货成功”之类后续流程。

4. 不检查影响行数

很多 SQL 没有报错,不代表业务成功。比如库存为 0 时,带 stock > 0 条件的扣减语句可能影响 0 行,这时就应该主动进入失败分支,而不是继续写订单明细。

落地速查表

动作 推荐写法 确认方式
设置错误模式 PDO::ERRMODE_EXCEPTION SQL 失败能进入错误处理
开启事务 beginTransaction() 放在第一条业务写入前
成功提交 commit() 所有写入和业务校验都通过
失败回滚 rollBack() 先确认仍在事务中
最终确认 查询主表、明细、库存 数量和状态一致

总结一下:PDO 事务的重点不是记住某几个方法名,而是把“开启、写入、校验、提交、回滚、确认”串成固定流程。只要每个阶段都有检查点,多表写入就不容易留下半截数据,后续排查也会更清楚。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
VS Code 怎么设置自动保存:Auto Save 与保存后格式化完整教程VS Code 怎么设置自动保存:Auto Save 与保存后格式化完整教程
上一篇
VS Code 怎么设置自动保存:Auto Save 与保存后格式化完整教程
Python requests 请求一直卡住怎么办:timeout、状态码和重试一步步排查
下一篇
Python requests 请求一直卡住怎么办:timeout、状态码和重试一步步排查
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    62次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    73次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    76次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    218次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    216次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码