PHP MySQL事务代码如何正确实现与回滚?
- 虚拟主机
- 2025-12-17
- 5
PHP与MySQL事务代码是实现数据库操作原子性、一致性、隔离性和持久性的关键,尤其在处理涉及多表操作的复杂业务逻辑时,事务能确保数据安全,以下从基础概念到代码实现,结合场景示例详细说明。
事务的核心特性与使用场景
事务的ACID特性是数据库可靠性的基石:原子性(Atomicity)确保事务要么全部执行,要么全部回滚;一致性(Consistency)保证事务结束后数据符合业务规则;隔离性(Isolation)防止并发事务相互干扰;持久性(Durability)确保事务提交后数据永久保存,典型应用场景包括银行转账(扣款与收款必须同时成功)、订单创建(扣除库存、生成订单记录、支付记录需同步)等。

PHP中使用MySQL事务的代码实现
在PHP中,通常通过MySQLi或PDO扩展操作数据库事务,以下以PDO为例(推荐,支持预处理语句且更安全)。
基本事务处理流程
try { // 1. 创建PDO连接,关闭自动提交(开启事务) $pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password'); $pdo>setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo>beginTransaction(); // 开启事务 // 2. 执行SQL语句(假设有两个操作:转账扣款和收款) $stmt1 = $pdo>prepare("UPDATE accounts SET balance = balance 100 WHERE user_id = 1"); $stmt1>execute(); $stmt2 = $pdo>prepare("UPDATE accounts SET balance = balance + 100 WHERE user_id = 2"); $stmt2>execute(); // 3. 提交事务(所有执行成功) $pdo>commit(); echo "事务提交成功,数据已更新"; } catch (Exception $e) { // 4. 发生异常时回滚事务 if (isset($pdo) && $pdo>inTransaction()) { $pdo>rollBack(); } echo "事务执行失败,已回滚:" . $e>getMessage(); }
事务中的错误处理与隔离级别
事务执行时需捕获异常(如SQL语法错误、主键冲突等),并通过rollBack()回滚,可通过设置隔离级别避免并发问题,

$pdo>setTransactionIsolation(PDO::TRANSACTION_READ_COMMITTED); // 设置为读已提交隔离级别
常见隔离级别对比:

| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 少数场景,性能优先 |
| READ COMMITTED(默认) | 不可能 | 可能 | 可能 | 大部分业务,平衡性能与一致性 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 | 需要多次读取数据一致的场景 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 高一致性要求,性能较低 |
复杂事务场景:多表操作与嵌套事务
以电商订单创建为例,涉及订单表、订单详情表、库存表,需确保操作同步:
$pdo>beginTransaction(); try { // 插入订单主表 $orderStmt = $pdo>prepare("INSERT INTO orders (user_id, total_amount) VALUES (?, ?)"); $orderStmt>execute([1, 200]); $orderId = $pdo>lastInsertId(); // 获取订单ID // 插入订单详情表 $detailStmt = $pdo>prepare("INSERT INTO order_details (order_id, product_id, quantity) VALUES (?, ?, ?)"); $detailStmt>execute([$orderId, 101, 2]); // 扣减库存 $stockStmt = $pdo>prepare("UPDATE products SET stock = stock ? WHERE product_id = ? AND stock >= ?"); $stockStmt>execute([2, 101, 2]); // 确保库存充足 $pdo>commit(); } catch (Exception $e) { $pdo>rollBack(); throw $e; // 抛出异常由上层处理 }
注意:MySQL原生不支持嵌套事务,但可通过“保存点”(Savepoint)模拟:
$pdo>beginTransaction(); $pdo>exec("INSERT INTO logs (message) VALUES ('事务开始')"); $pdo>exec("SAVEPOINT nested_sp"); // 创建保存点 try { // 执行嵌套操作 $pdo>exec("UPDATE accounts SET balance = balance 50 WHERE user_id = 3"); // 假设此处触发异常 throw new Exception("模拟嵌套操作失败"); } catch (Exception $e) { $pdo>exec("ROLLBACK TO nested_sp"); // 回滚到保存点,不影响外层事务 echo "嵌套操作回滚:" . $e>getMessage(); } $pdo>commit(); // 外层事务继续提交
最佳实践与注意事项
- 事务范围最小化:避免将非数据库操作(如API调用、文件写入)放入事务,延长锁定时间。
- 资源释放:确保在finally块中关闭PDO语句($stmt = null),防止连接泄漏。
- 超时处理:通过PDO::ATTR_TIMEOUT设置事务超时(单位秒),避免长时间锁定。
- 事务隔离级别选择:根据业务需求权衡,例如高并发抢票场景需SERIALIZABLE防止超卖。
相关问答FAQs
Q1: 事务中遇到死锁如何处理?
A: 死锁多因事务间资源竞争(如事务A锁表1等待表2,事务B锁表2等待表1),解决方法:① 优化事务顺序,减少锁竞争;② 设置较短的事务超时时间;③ 代码中捕获死锁异常(错误码1213),重试事务(通常23次)。
Q2: 事务提交后数据未持久化怎么办?
A: 首先检查数据库配置(如innodb_flush_log_at_trx_commit参数是否为1,确保每次提交日志落盘);其次确认代码未异常终止(如PHP脚本超时、内存不足);最后检查MySQL服务器是否正常关闭(如fast_shutdown导致日志未完全刷新)。