本文目录导读:

在PHP项目中实现数据回滚,主要依靠数据库事务(Transaction)机制,事务允许你将一组数据库操作(如插入、更新、删除)打包成一个原子单元:要么全部成功执行(提交),要么全部失败回滚(撤销)。
以下是几种常见的实现方式,从基础到进阶:
使用 PDO 实现事务回滚(最推荐)
PDO(PHP Data Objects)提供了简洁、统一的数据库事务控制。
<?php
try {
// 1. 创建 PDO 连接,并设置错误模式为异常
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 2. 关闭自动提交,开启事务
$pdo->beginTransaction();
// 3. 执行多个数据库操作(转账操作)
// 从账户 A 扣钱
$sql1 = "UPDATE accounts SET balance = balance - 100 WHERE id = 1";
$pdo->exec($sql1);
// 向账户 B 加钱
$sql2 = "UPDATE accounts SET balance = balance + 100 WHERE id = 2";
$pdo->exec($sql2);
// 4. 如果没有异常,提交事务
$pdo->commit();
echo "转账成功!";
} catch (PDOException $e) {
// 5. 如果任何一步出错,回滚事务
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo "转账失败,已回滚:" . $e->getMessage();
}
?>
关键点:
beginTransaction():开启事务,PDO 会暂时关闭自动提交。commit():所有操作成功,将更改永久写入数据库。rollBack():发生异常,撤销本次事务中的所有操作。inTransaction():检查当前是否在事务中,避免在非事务状态下调用rollBack()引发错误。
使用 MySQLi 实现事务回滚
如果你仍在使用 MySQLi 扩展,也可以实现类似功能:
<?php
$mysqli = new mysqli('localhost', 'root', 'password', 'test');
if ($mysqli->connect_error) {
die("连接失败: " . $mysqli->connect_error);
}
// 关闭自动提交
$mysqli->autocommit(FALSE);
try {
// 执行操作
$result1 = $mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$result2 = $mysqli->query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
if ($result1 && $result2) {
$mysqli->commit();
echo "提交成功";
} else {
throw new Exception($mysqli->error);
}
} catch (Exception $e) {
$mysqli->rollback();
echo "回滚成功:" . $e->getMessage();
}
$mysqli->close();
?>
使用 ORM 框架(如 Laravel、ThinkPHP)
现代框架通常封装了事务机制,使用更便捷,以 Laravel 为例:
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
// 所有操作默认在一个事务中
DB::table('accounts')->where('id', 1)->decrement('balance', 100);
DB::table('accounts')->where('id', 2)->increment('balance', 100);
// 如果这里抛出异常,框架会自动回滚
});
如果需要手动控制提交/回滚:
DB::beginTransaction();
try {
// 操作...
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
// 记录日志或返回错误
}
ThinkPHP 6 示例:
Db::transaction(function () {
Db::table('accounts')->where('id', 1)->dec('balance', 100);
Db::table('accounts')->where('id', 2)->inc('balance', 100);
});
跨服务 / 多数据库事务回滚(分布式事务)
当你的操作涉及多个数据库或不同服务(如 MySQL + Redis + 第三方 API),单数据库事务就不够了,常见的解决方案:
- 两阶段提交(2PC):协调器先准备所有参与者,再统一提交或回滚,实现复杂,性能有损耗。
- Saga 模式:将大事务拆分为多个本地子事务,每个子事务有对应的补偿操作(回滚逻辑),适用于微服务。
- 消息队列 + 最终一致性:先执行核心操作,失败时通过消息队列触发补偿或异步重试。
PHP 中简单实现补偿逻辑示例(手动回滚):
$completedSteps = [];
try {
// 步骤1:更新 MySQL
$pdo->exec("UPDATE ...");
$completedSteps[] = 'mysql';
// 步骤2:删除 Redis 缓存
$redis->del('key');
$completedSteps[] = 'redis';
// 步骤3:调用外部 API
$api->transfer(...);
$completedSteps[] = 'api';
} catch (\Exception $e) {
// 根据已完成的步骤,逆向执行补偿操作
if (in_array('api', $completedSteps)) {
$api->rollbackTransfer(...); // 调用外部API的撤销接口
}
if (in_array('redis', $completedSteps)) {
$redis->set('key', $oldValue); // 恢复旧值
}
if (in_array('mysql', $completedSteps)) {
$pdo->exec("ROLLBACK"); // MySQL 事务回滚
}
// 记录错误日志
}
数据库引擎要求
事务回滚的前提是数据库表引擎支持事务:
- MySQL:使用
InnoDB引擎(MyISAM不支持事务)。 - PostgreSQL:默认支持。
- SQLite:支持但写性能较弱。
检查/修改 MySQL 表引擎:
-- 查看引擎 SHOW TABLE STATUS LIKE 'your_table'; -- 改为 InnoDB ALTER TABLE your_table ENGINE = InnoDB;
最佳实践建议
| 场景 | 推荐方案 |
|---|---|
| 简单 PHP 项目 | PDO 事务(轻量、安全、跨数据库) |
| 使用框架 | 框架自带事务(Laravel DB::transaction(),ThinkPHP Db::transaction()) |
| 跨服务/分布式 | Saga 模式 或 消息队列最终一致性 |
| 复杂业务逻辑 | 封装一个 TransactionManager 类,统一管理 begin/commit/rollback |
核心原则:
- 所有写操作(增、删、改)尽量放在事务中。
- 事务内不要包含耗时的外部调用(如 HTTP 请求、文件读写),以免锁表。
- 记得捕获异常,确保任何失败都能触发
rollBack()。 - 数据库连接必须持久或重用同一个连接(事务是基于连接的)。