本文目录导读:

在 PHP 中实现“回滚策略”,主要取决于你是在处理数据库事务、文件操作还是多步骤业务流程。
以下是最常见场景的详细回滚实现方式:
数据库事务回滚(最核心、最常用)
这是最标准的回滚场景,用于确保数据库操作的原子性(要么全部成功,要么全部失败)。
使用 PDO(推荐)
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 步骤1: 开启事务
$pdo->beginTransaction();
// 步骤2: 执行多个SQL
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
// 步骤3: 检查业务逻辑(例如余额不能为负)
$stmt = $pdo->query("SELECT balance FROM accounts WHERE id = 1");
$balance = $stmt->fetchColumn();
if ($balance < 0) {
throw new Exception("余额不足,触发回滚");
}
// 步骤4: 全部成功 -> 提交
$pdo->commit();
echo "转账成功!";
} catch (Exception $e) {
// 步骤5: 出错 -> 回滚
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
echo "转账失败:" . $e->getMessage();
}
?>
关键点:
beginTransaction()+rollBack()必须在同一个连接上执行,且rollBack()只回滚当前连接未提交的事务。
使用 MySQLi
<?php
$mysqli = new mysqli('localhost', 'root', '', 'test');
$mysqli->autocommit(FALSE); // 关闭自动提交,相当于开启事务
try {
$mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
if ($mysqli->affected_rows === 0) {
throw new Exception("更新失败");
}
$mysqli->query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$mysqli->commit();
echo "成功";
} catch (Exception $e) {
$mysqli->rollback();
echo "回滚: " . $e->getMessage();
}
?>
多步骤文件/状态回滚(手动实现)
当业务涉及多个外部系统(如:发短信、写文件、调用API),你需要手动记录已执行的步骤,失败时反向撤销。
示例:用户注册(插入数据库 + 发送邮件 + 创建目录)
<?php
class UserRegistrationService {
private $steps = []; // 记录已执行的步骤
private $pdo;
public function register($username) {
try {
$this->pdo->beginTransaction();
// 步骤1: 写入数据库
$stmt = $this->pdo->prepare("INSERT INTO users (username) VALUES (?)");
$stmt->execute([$username]);
$userId = $this->pdo->lastInsertId();
$this->steps[] = 'database';
// 步骤2: 创建用户目录
$dir = "./uploads/{$userId}";
if (!mkdir($dir, 0755, true)) {
throw new Exception("创建目录失败");
}
$this->steps[] = 'directory';
// 步骤3: 发送欢迎邮件(假设可能失败)
if (!$this->sendWelcomeEmail($username)) {
throw new Exception("邮件发送失败");
}
$this->steps[] = 'email';
$this->pdo->commit();
return true;
} catch (Exception $e) {
// 回滚策略:反向撤销已执行的步骤
$this->rollbackPreviousSteps($userId ?? null);
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw $e;
}
}
private function rollbackPreviousSteps($userId) {
// 按相反顺序回滚
$steps = array_reverse($this->steps);
foreach ($steps as $step) {
switch ($step) {
case 'email':
// 无法真正撤销已发送的邮件,记录日志
error_log("无法回滚已发送的邮件");
break;
case 'directory':
if ($userId) {
$dir = "./uploads/{$userId}";
if (is_dir($dir)) {
rmdir($dir); // 删除目录
}
}
break;
case 'database':
// 数据库回滚由 PDO->rollBack() 负责
break;
}
}
}
private function sendWelcomeEmail($username) {
// 模拟发送邮件,可能失败
return rand(0, 1) === 1; // 50% 概率失败
}
}
// 使用
$service = new UserRegistrationService();
try {
$service->register('john_doe');
echo "注册成功";
} catch (Exception $e) {
echo "注册失败:" . $e->getMessage();
}
?>
文件系统事务(使用临时文件 + 重命名)
这是一种流行的原子性文件操作策略,特别适合处理大文件或配置更新。
<?php
$targetFile = '/var/www/html/config.php';
$tempFile = $targetFile . '.tmp';
// 步骤1: 先写入临时文件
$data = "<?php\n\$config = ['status' => 'updated'];\n";
file_put_contents($tempFile, $data);
// 步骤2: 验证临时文件完整性(例如校验hash)
if (md5_file($tempFile) !== 'expected_md5') {
unlink($tempFile); // 删除临时文件 = 回滚
die("校验失败,已回滚");
}
// 步骤3: 原子重命名(同一文件系统内,rename是原子的)
if (rename($tempFile, $targetFile)) {
echo "成功";
} else {
unlink($tempFile); // 重命名失败,删除临时文件
echo "失败";
}
?>
分布式/微服务回滚(Saga 模式)
在微服务架构中,跨服务事务无法使用数据库本地事务,这是理论和代码实现相对复杂的场景,常用模式是 Saga(长事务):
- Choreography(编排式):每个服务执行后发出事件,下一个服务监听执行;失败时由触发补偿事件。
- Orchestration(编舞式):一个协调器负责调用每个服务,并记录状态,失败时调用每个服务的回滚 API。
简单实现(编舞式)
<?php
class SagaCoordinator {
private $executedServices = [];
public function execute(ServiceInterface ...$services) {
foreach ($services as $service) {
try {
$service->execute();
$this->executedServices[] = $service;
} catch (Exception $e) {
// 回滚所有已执行的服务
foreach (array_reverse($this->executedServices) as $executedService) {
try {
$executedService->rollback();
} catch (Exception $rollbackEx) {
error_log("回滚失败: {$rollbackEx->getMessage()}");
}
}
throw $e;
}
}
}
}
// 使用
$coordinator = new SagaCoordinator();
$coordinator->execute(
new PaymentService(),
new InventoryService(),
new NotificationService()
);
?>
选择最优策略
| 场景 | 推荐策略 |
|---|---|
| 单数据库操作 | 使用 PDO / MySQLi 的 beginTransaction() + rollBack()(最标准) |
| 单服务器文件+数据库 | 手动记录步骤,按反向顺序撤销(第2节) |
| 配置文件/大文件更新 | 使用临时文件 + rename() 原子操作(第3节) |
| 微服务跨系统事务 | 使用消息队列(补偿事件)或 Saga 模式(第4节) |
| 内存中操作 | 使用 try/finally 和变量快照恢复状态 |
核心原则:
- 尽早失败:在业务校验完成后再提交事务。
- 补偿幂等:回滚操作最好是幂等的(多次执行结果一样)。
- 不可逆操作:如“发送邮件”无法撤回,需通过业务补偿(如发送退款/撤销邮件)。
如果你能提供更具体的业务场景(“我有10个步骤,失败后只回滚后5个”),我可以给出更精确的代码实现。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播