本文目录导读:

- 使用 PDO(PHP Data Objects)进行回滚
- 使用 MySQLi(MySQL Improved)进行回滚
- 使用 Laravel(Eloquent ORM)进行回滚
- 必须注意的关键点(避免常见错误)
- 完整示例:带日志和回滚的银行转账
- 总结操作流程
在PHP中实现数据回滚,主要依赖于数据库事务(Transaction),核心思路是:将多个数据库操作(增、删、改)组合成一个原子操作,要么全部成功(Commit),要么全部失败(Rollback)。
以下是三种主流方式的操作详解:
使用 PDO(PHP Data Objects)进行回滚
这是最推荐的方式,支持多种数据库(MySQL、PostgreSQL等)。
<?php
try {
// 1. 创建 PDO 连接(务必关闭自动提交)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 2. 关闭自动提交,开始事务
$pdo->beginTransaction();
// 3. 执行多个数据操作
// 操作1: 更新用户余额
$sql1 = "UPDATE users SET balance = balance - 100 WHERE id = 1";
$pdo->exec($sql1);
// 操作2: 插入订单记录
$sql2 = "INSERT INTO orders (user_id, amount, status) VALUES (1, 100, 'pending')";
$pdo->exec($sql2);
// 操作3: 检查用户余额是否足够(模拟业务逻辑检查)
$stmt = $pdo->query("SELECT balance FROM users WHERE id = 1");
$user = $stmt->fetch();
if ($user['balance'] < 0) {
// 余额不足,抛出异常触发回滚
throw new Exception("余额不足,需要回滚!");
}
// 4. 全部成功,提交事务
$pdo->commit();
echo "操作成功完成!";
} catch (Exception $e) {
// 5. 发生任何异常,回滚事务
$pdo->rollBack();
echo "操作失败,已回滚:" . $e->getMessage();
}
?>
关键点:
PDO::ATTR_ERRMODE设为异常模式,方便捕获错误。beginTransaction()自动关闭自动提交。rollBack()撤销 beginTransaction 之后的所有操作。
使用 MySQLi(MySQL Improved)进行回滚
适用于项目已使用 MySQLi 扩展的情况。
<?php
$mysqli = new mysqli("localhost", "root", "password", "test");
// 检查连接
if ($mysqli->connect_error) {
die("连接失败: " . $mysqli->connect_error);
}
// 设置自动提交为 false
$mysqli->autocommit(FALSE);
try {
// 操作1: 扣钱
$result1 = $mysqli->query("UPDATE users SET balance = balance - 100 WHERE id = 1");
if (!$result1) throw new Exception("扣钱失败");
// 操作2: 加钱到另一个账户
$result2 = $mysqli->query("UPDATE users SET balance = balance + 100 WHERE id = 2");
if (!$result2) throw new Exception("加钱失败");
// 全部成功 → 提交
$mysqli->commit();
echo "转账成功";
} catch (Exception $e) {
// 失败 → 回滚
$mysqli->rollback();
echo "转账失败,已回滚:" . $e->getMessage();
}
$mysqli->close();
?>
注意:MySQLi 的 autocommit(FALSE) 和 commit() / rollback() 手动控制事务。
使用 Laravel(Eloquent ORM)进行回滚
如果你使用 Laravel 框架,有更简洁的语法:
use Illuminate\Support\Facades\DB;
try {
DB::beginTransaction();
// 业务操作
$user = User::find(1);
$user->balance -= 100;
$user->save();
Order::create([
'user_id' => 1,
'amount' => 100
]);
// 手动检查并抛异常
if ($user->balance < 0) {
throw new \Exception("余额不足");
}
DB::commit();
return "操作成功";
} catch (\Exception $e) {
DB::rollBack();
return "操作失败,已回滚:" . $e->getMessage();
}
必须注意的关键点(避免常见错误)
| 问题 | 正确做法 |
|---|---|
| 引擎必须是 InnoDB | MyISAM 引擎不支持事务,操作不会回滚,检查表引擎:SHOW TABLE STATUS LIKE 'your_table' |
| 捕获所有异常 | 要捕获 Exception 或 Throwable,不能只捕获特定错误,否则未捕获的错误会跳过 rollback |
不要在事务中使用 die() 或 exit() |
会导致连接断开,事务自动回滚但可能不完整 |
| 不要在事务内部混合多层 try-catch | 确保一个事务对应一个 try-catch 块,否则可能误提交或重复回滚 |
| 注意嵌套事务 | PHP 原生 PDO 不原生支持嵌套事务(MySQL 也不支持),Laravel 等框架会模拟,但建议避免复杂的嵌套 |
完整示例:带日志和回滚的银行转账
<?php
function transferMoney($fromId, $toId, $amount) {
$pdo = new PDO('mysql:host=localhost;dbname=bank', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
$pdo->beginTransaction();
// 扣款
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?");
$stmt->execute([$amount, $fromId, $amount]);
if ($stmt->rowCount() === 0) {
throw new Exception("扣款失败(余额不足或账户不存在)");
}
// 入账
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
$stmt->execute([$amount, $toId]);
if ($stmt->rowCount() === 0) {
throw new Exception("入账失败(收款账户不存在)");
}
// 记录日志
$pdo->exec("INSERT INTO transfer_log (from_id, to_id, amount, time) VALUES ($fromId, $toId, $amount, NOW())");
$pdo->commit();
return "转账成功";
} catch (Exception $e) {
$pdo->rollBack();
error_log("转账失败: " . $e->getMessage());
return "转账失败,交易已撤销";
}
}
总结操作流程
- 关闭自动提交 / 开始事务
- 执行所有业务操作
- 进行业务规则校验(余额检查、唯一性检查等)
- 全部成功 → 提交
- 任一失败 → 回滚
这就是 PHP 中实现数据回滚的核心方式,如果你使用的是特定框架(ThinkPHP、Symfony 等),通常都有类似 Db::transaction() 的便捷方法,原理相同。