滚动新闻 →
Windows 11 Wi-Fi 连接正常却无法上网怎么办?一套基础排查方法 中期选举后共和党内部反思:川普政治遗产与未来路线之争 MySQL LIKE 查询为什么可能很慢?文章搜索功能应该怎样设计 中环碁圣赛四强赛落幕 林君谚卢奕铨晋级决赛 闪电战!美狂轰伊朗要塞 白宫惊传暗杀! 欧洲超市制冷系统遭大规模网络攻击 专家指涉国家行为体 立冬养生需注意食补禁忌 中国古典文学中的饮酒文化 十二经脉在传统理论中的位置 不粘锅使用隐患解析:专家揭示四大警示信号 阳刻和阴刻有什么区别 河北惊传在建大桥坍塌 事发瞬间画面曝光 让孩子懂得“不是故意的”也不等于不用负责 台籍诈骗集团头目在菲落网 涉案金额逾1亿元 白宫新幕僚长誓言忠诚,承诺全心全意服务总统与国家 泰国曼谷知名商场坠楼案 40岁台湾男子重伤不治 如何判断一个镜头应该保留还是删除 云南影视拍摄现场突发火灾 男主角疑似烧伤 中共二十一大前高层各怀算计 有致命软肋 洋务运动为什么没有实现真正的强国目标 男子利用AI换脸冒用他人护照被识破 无人机袭击致海地黑帮头目身亡 房间里的凹角空间可以怎么利用 巴拉圭副总统阿里亚纳意外成为总统 篮球国手转型政坛 美国多州税收政策分化 中产家庭面临福利困境 4名少将及5人辞去全国人大代表职务 大陆AI换脸带货黑产遭重拳打击 近4000账号被封禁 河北前委书记倪岳峰被调任人大 涉嫌经济问题遭调查 中国人的“夹菜”习惯有什么文化背景 委内瑞拉移民持枪威胁川普 被捕于白宫周边 巴拉圭国会弹劾总统培尼亚 银行倒闭丑闻引争议 在加拿大特菲诺看日落,太平洋的颜色每天都不一样 安徽女子遭陌生人泼汽油致90%烧伤 三子女辍学打工筹医疗费 进化骗局(35) 南猿露西 百谎难圆 山西走失13年女硕士被寻回 父亲斥其为‘老色、鬼’ 美国邮政署启动圣诞礼物认领计划 儿童心愿有望实现 江苏沛县家长指控教师暴力致伤 学校管理问题引争议 英国出土2700年古井 井壁为完整树干 黄仁勋被传唤出席国会听证会,黄乐被指为中共间谍 燃油压力不足会造成什么问题

PHP 数据库事务怎么使用?涉及多张表的数据操作需要这样处理

发布时间: 2026-10-18 05:24:01    最后更新: 2026-11-07 07:20:55    阅读:78  约3 分钟阅读     

PHP 数据库事务怎么使用?涉及多张表的数据操作需要这样处理
图片说明:示意图   图片来源:Public Domain(公有领域)
在实际网站开发中,一项业务往往不只涉及一张数据库表。例如,用户注册可能需要同时写入用户表和积分表;创建订单时,系统可能需要更新订单表、扣减库存,并写入支付或操作日志。如果其中一个步骤成功,另一个步骤却失败,就可能出现用户已经创建、积分却没有到账,或者订单已经生成、库存却没有减少的情况。 数据库事务的作用,就是把这些相互关联的操作纳入同一个可控的执行范围。在符合事务条件的情况下,程序可以要求这些操作要么全部提交,要么在发生错误时全部回滚,从而避免部分业务数据被保存。事务并不是PHP独有的功能,而是由数据库管理系统提供、再通过PDO或MySQLi等接口进行控制的机制。 事务的核心原理 事务通常用ACID四个特性来描述。原子性(Atomicity)意味着一组相关操作作为一个整体执行,不能只完成其中一部分;一致性(Consistency)意味着事务执行前后,数据库应当继续满足既定的约束和业务规则;隔离性(Isolation)用于控制并发事务之间的相互影响;持久性(Durability)则表示事务成功提交后,已经保存的数据不会因为普通程序结束而自动消失。 需要注意,事务并不能自动修复所有业务错误。例如,程序虽然成功扣减了库存,但如果库存扣减逻辑本身写错,事务仍然可能把错误结果完整提交。因此,事务负责控制一组操作的提交与回滚,业务规则、数据约束和正确的SQL逻辑仍然需要由开发者设计。 PHP中如何开启和结束事务 使用PDO时,最常见的事务控制方法包括beginTransaction()、commit()和rollBack()。调用beginTransaction()后,当前PDO连接进入事务状态;执行期间的相关数据变更不会在每条语句执行后立即提交。所有步骤成功后调用commit(),发生异常或业务校验失败时,则调用rollBack()。 下面以用户注册为例。假设系统需要同时向users表和points表写入数据,示例使用MySQL的InnoDB存储引擎,并假设两张表均已建立适当的字段、主键和外键约束。 setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); $username = 'john'; $email = 'john@example.com'; try { $pdo->beginTransaction(); // 第一步:创建用户 $stmt = $pdo->prepare(" INSERT INTO users (username, email) VALUES (?, ?) "); $stmt->execute([ $username, $email ]); // 获取当前连接刚刚生成的用户ID $userId = $pdo->lastInsertId(); // 第二步:为用户建立积分记录 $stmt = $pdo->prepare(" INSERT INTO points (user_id, points) VALUES (?, ?) "); $stmt->execute([ $userId, 100 ]); // 所有操作成功后提交 $pdo->commit(); echo '用户注册成功'; } catch (Throwable $e) { // 只有在事务仍然处于活动状态时才执行回滚 if ($pdo->inTransaction()) { $pdo->rollBack(); } // 生产环境中不要直接向用户输出数据库内部错误 error_log( '用户注册事务失败:' . $e->getMessage() ); echo '操作失败,请稍后重试'; } 这段代码有几个关键点。第一,必须使用同一个PDO连接完成所有数据库操作。第二,用户ID应当在插入用户记录之后立即获取,并通过参数传递给下一条SQL。第三,使用预处理语句可以避免把用户输入直接拼接进SQL。第四,catch中不能无条件调用rollBack(),因为异常可能发生在事务开启之前,也可能发生在事务已经提交之后;使用inTransaction()进行判断更加稳妥。 为什么不能只依靠程序中的判断 有些开发者会采用这样的逻辑:先执行一条SQL,判断返回值是否成功,再执行下一条SQL。如果第二条SQL失败,就在程序中尝试删除第一条SQL写入的数据。这种做法在简单场景下看似可行,但并不等同于真正的事务。 例如,第一条操作成功后,程序在执行第二条操作之前突然中断,后续的删除代码就没有机会执行。又或者,第一条操作写入的数据已经被其他程序读取,手动补偿删除可能引发新的业务问题。事务由数据库统一管理,能够在事务范围内按照提交或回滚规则处理数据变化,比依靠多段人工补偿代码更加可靠。 不过,事务也有边界。它通常只对支持事务的存储引擎有效。在MySQL中,如果相关表使用不支持事务的存储引擎,即使PHP代码成功调用了beginTransaction(),也不能因此认为所有数据变更都能够回滚。涉及多表操作时,应确认相关表使用InnoDB等支持事务的存储引擎。 多表事务不等于锁住整张表 事务执行期间,数据库可能根据SQL语句、索引、存储引擎和隔离级别,对相关记录或索引范围加锁,但这并不意味着整个数据库表都会被锁住。锁的范围取决于具体操作。 例如,更新某个用户的积分记录时,如果user_id上存在合适的索引,数据库通常可以更精确地定位需要处理的记录。反过来,如果查询条件缺少索引,数据库可能需要扫描更多数据,进而增加锁竞争和执行成本。因此,事务设计不能只关注PHP代码,也必须考虑数据库表结构、索引和SQL执行计划。 事务中的操作应当尽量短小。不要在事务开启后执行长时间的网络请求、等待用户输入、调用外部API或进行复杂文件处理。更合理的做法是先完成不需要数据库锁的准备工作,随后快速开启事务,完成必要的数据读写,最后立即提交。 订单、库存和支付记录应该如何处理 订单系统是多表事务的典型应用场景。创建订单时,系统可能需要执行以下操作:建立订单主记录、写入订单明细、检查并扣减库存、写入支付待处理记录。如果这些步骤必须同时成功才能构成完整业务,就可以将适当的数据操作放入同一个事务。 但库存扣减不能只写成简单的“读取库存,再减去购买数量”。在并发环境下,两个用户可能同时购买最后一件商品。如果程序先读取库存,再分别执行更新,就可能出现超卖。因此,库存操作应当结合条件更新、行级锁或其他并发控制机制。例如,可以使用带有库存条件的更新语句: $stmt = $pdo->prepare(" UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ? "); $stmt->execute([ $quantity, $productId, $quantity ]); if ($stmt->rowCount() !== 1) { throw new RuntimeException('库存不足'); } 这条SQL通过stock >= ?限制了扣减条件,程序还需要检查实际受影响的行数。如果没有成功扣减库存,就抛出异常,让外层事务回滚订单及其他相关数据。 支付系统则需要更加谨慎。数据库事务可以保证订单记录和支付状态在数据库中的一致性,但它无法把外部支付平台的扣款动作自动纳入同一个本地数据库事务。支付平台调用失败、网络超时或重复通知,都需要通过幂等键、支付状态机、回调验证和必要的补偿机制进行处理。不能简单地认为“数据库事务提交成功”就等于“支付已经完成”。 隔离级别和并发问题 数据库隔离级别决定了并发事务在读取和修改数据时能够看到什么,以及可能出现哪些并发现象。常见级别包括READ COMMITTED、REPEATABLE READ和SERIALIZABLE。隔离级别越严格,通常意味着更强的并发约束,但也可能带来更多锁竞争或性能成本。 MySQL的InnoDB默认使用REPEATABLE READ。开发者不应为了追求“绝对安全”就盲目把所有业务设置为最高隔离级别,而应根据业务场景、查询方式、锁策略和并发压力进行测试。对于库存、余额、账户转账等业务,仅仅调整隔离级别通常还不够,还需要使用正确的条件更新、行锁、唯一约束和业务校验。 死锁为什么会发生 当两个事务分别持有对方需要的锁,并且都在等待对方释放资源时,就可能发生死锁。数据库通常会检测到死锁,并主动回滚其中一个事务,让另一个事务继续执行。 因此,程序不能只记录死锁错误然后直接向用户返回失败。在适合重试的业务中,可以针对明确的死锁或瞬时锁等待错误进行有限次数的重试。不过,重试前必须确认事务已经回滚或当前连接已经恢复到可用状态,而且业务操作必须具备幂等性。涉及扣款、发货和外部接口调用的操作,不能不加区分地自动重复执行。 减少死锁的常见方法包括:让不同业务按照一致的顺序访问多张表或多组记录;为查询条件建立合适索引;缩短事务持续时间;避免在事务中执行不必要的查询和更新;分析数据库错误日志和死锁报告,而不是单纯依靠增加等待时间解决问题。 事务中不能随意执行建表和改表操作 事务主要用于处理业务数据,而不是随意混合数据库结构变更。在MySQL等数据库中,部分DDL语句,例如CREATE TABLE、ALTER TABLE和DROP TABLE,可能触发隐式提交。这样一来,开发者原本以为可以通过rollBack()撤销的操作,实际上可能已经被提交,无法恢复。 因此,建表、改字段、创建索引等数据库结构变更,应当通过独立的数据库迁移流程执行,不要与用户注册、订单创建等业务事务混在一起。 实际开发中的检查清单 使用PHP事务时,可以重点检查以下事项: 所有相关表是否使用支持事务的存储引擎。 多张表的操作是否使用同一个PDO连接。 是否开启了PDO::ERRMODE_EXCEPTION。 是否使用预处理语句处理外部输入。 事务是否只包含必要的数据库操作。 是否在提交前检查业务条件和SQL执行结果。 回滚前是否确认当前确实存在活动事务。 是否建立主键、外键、唯一索引和必要的业务约束。 是否考虑并发更新、锁等待和死锁重试。 是否避免在事务中执行可能触发隐式提交的DDL操作。 是否针对异常中断、重复请求和并发操作进行测试。 PHP数据库事务的价值,不只是让多条SQL语句“同时执行”,而是让一组具有业务关联的数据变化拥有明确的提交边界。它可以防止一个业务流程只完成一半,却把残缺结果永久写入数据库。但事务不是万能的保险箱:存储引擎、索引、锁策略、异常处理、幂等设计和业务规则,都会影响最终的数据可靠性。真正成熟的系统,不仅要知道什么时候调用beginTransaction(),更要知道哪些操作必须放在同一个事务里,哪些操作必须留在事务之外,以及发生并发冲突时应该如何安全恢复。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP