PHP PDO::rollBack 方法
最后修改于 2025 年 4 月 19 日
PDO::rollBack 方法用于撤销在事务期间所做的更改。它会将数据库恢复到事务开始之前的状态。
基本定义
PDO::rollBack 回滚当前事务。仅当数据库支持事务且自动提交已关闭时才有效。
语法:public PDO::rollBack(): bool。成功返回 true,失败返回 false。如果不存在活动事务,则抛出 PDOException。
基本事务回滚示例
这显示了在发生错误时 rollBack 的最简单用法。
basic_rollback.php
<?php
declare(strict_types=1);
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$pdo->exec("INSERT INTO users (name, email) VALUES ('John', 'john@example.com')");
// Simulate an error
throw new Exception("Something went wrong");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo "Transaction rolled back: " . $e->getMessage();
}
这启动了一个事务,插入了一条记录,然后模拟了一个错误。catch 块调用 rollBack 来撤销插入操作。数据库保持不变。
嵌套事务与回滚
这演示了嵌套事务的回滚行为。
nested_rollback.php
<?php
declare(strict_types=1);
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Outer transaction
$pdo->beginTransaction();
$pdo->exec("INSERT INTO logs (message) VALUES ('Starting process')");
try {
// Inner transaction
$pdo->beginTransaction();
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
// Simulate error in inner transaction
throw new Exception("Transfer failed");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack(); // Rolls back only the inner transaction
echo "Inner transaction failed: " . $e->getMessage();
}
$pdo->commit(); // Commits the outer transaction
echo "Outer transaction completed";
} catch (PDOException $e) {
$pdo->rollBack(); // Rolls back everything if outer transaction fails
echo "Error: " . $e->getMessage();
}
这展示了嵌套事务。内部的 rollBack 只会撤销内部操作。外部事务仍然可以成功提交。
条件回滚示例
这演示了基于业务逻辑条件使用 rollBack。
conditional_rollback.php
<?php
declare(strict_types=1);
try {
$pdo = new PDO('mysql:host=localhost;dbname=bank', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
// Withdraw from account 1
$pdo->exec("UPDATE accounts SET balance = balance - 200 WHERE id = 1");
// Check if balance went negative
$stmt = $pdo->query("SELECT balance FROM accounts WHERE id = 1");
$balance = $stmt->fetchColumn();
if ($balance < 0) {
$pdo->rollBack();
echo "Transaction rolled back due to insufficient funds";
} else {
// Deposit to account 2
$pdo->exec("UPDATE accounts SET balance = balance + 200 WHERE id = 2");
$pdo->commit();
echo "Transaction completed successfully";
}
} catch (PDOException $e) {
$pdo->rollBack();
echo "Error: " . $e->getMessage();
}
当账户余额变为负数时,此代码执行条件回滚。业务逻辑决定是提交还是回滚事务。
带保存点的回滚
这展示了使用保存点进行部分事务回滚。
savepoint_rollback.php
<?php
declare(strict_types=1);
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
// First operation
$pdo->exec("INSERT INTO orders (product, quantity) VALUES ('Laptop', 1)");
$pdo->exec("SAVEPOINT point1");
// Second operation
$pdo->exec("UPDATE inventory SET stock = stock - 1 WHERE product = 'Laptop'");
// Check stock level
$stmt = $pdo->query("SELECT stock FROM inventory WHERE product = 'Laptop'");
$stock = $stmt->fetchColumn();
if ($stock < 0) {
$pdo->exec("ROLLBACK TO SAVEPOINT point1");
echo "Partial rollback performed, order kept but inventory not updated";
}
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
echo "Error: " . $e->getMessage();
}
这在第一个操作后创建一个保存点。如果第二个操作的检查失败,它将回滚到保存点,而不是整个事务。
批量处理中的回滚
这演示了在处理批量记录时使用 rollBack。
batch_rollback.php
<?php
declare(strict_types=1);
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$records = [
['name' => 'Alice', 'email' => 'alice@example.com'],
['name' => 'Bob', 'email' => 'bob@example.com'],
['name' => '', 'email' => 'invalid'], // Invalid record
['name' => 'Charlie', 'email' => 'charlie@example.com']
];
$pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
foreach ($records as $record) {
if (empty($record['name']) || empty($record['email'])) {
$pdo->rollBack();
throw new Exception("Invalid record found, rolling back entire batch");
}
$stmt->execute([$record['name'], $record['email']]);
}
$pdo->commit();
echo "Batch processed successfully";
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
这在一个事务中处理多条记录。如果任何记录无效,则回滚整个批量。这确保了数据的一致性。
多数据库回滚
这展示了如何处理跨多个数据库连接的回滚。
multi_db_rollback.php
<?php
declare(strict_types=1);
try {
// First database connection
$pdo1 = new PDO('mysql:host=localhost;dbname=db1', 'user', 'password');
$pdo1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Second database connection
$pdo2 = new PDO('mysql:host=localhost;dbname=db2', 'user', 'password');
$pdo2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo1->beginTransaction();
$pdo2->beginTransaction();
try {
$pdo1->exec("INSERT INTO orders (product) VALUES ('Phone')");
$orderId = $pdo1->lastInsertId();
$pdo2->exec("INSERT INTO shipments (order_id) VALUES ($orderId)");
// Simulate error
throw new Exception("Shipping service unavailable");
$pdo1->commit();
$pdo2->commit();
} catch (Exception $e) {
$pdo1->rollBack();
$pdo2->rollBack();
echo "Distributed transaction rolled back: " . $e->getMessage();
}
} catch (PDOException $e) {
echo "Connection error: " . $e->getMessage();
}
这协调了两个数据库之间的事务。如果任何操作失败,两个事务都将被回滚。这维护了跨系统的 一致性。
带错误日志的回滚
这演示了在回滚事务时记录错误。
logging_rollback.php
<?php
declare(strict_types=1);
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$pdo->exec("UPDATE products SET stock = stock - 5 WHERE id = 101");
$pdo->exec("INSERT INTO order_items (product_id, quantity) VALUES (101, 5)");
// Check if product exists
$stmt = $pdo->query("SELECT COUNT(*) FROM products WHERE id = 101");
if ($stmt->fetchColumn() == 0) {
$pdo->rollBack();
// Log the error
$errorMsg = "Attempted to order non-existent product 101";
$pdo->exec("INSERT INTO error_log (message) VALUES ('$errorMsg')");
throw new Exception($errorMsg);
}
$pdo->commit();
echo "Order processed successfully";
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
这回滚了主事务,但将错误记录到单独的表中。错误日志记录发生在回滚之后,以确保它始终被记录。
rollBack 的最佳实践
- 错误处理: 始终在事务中使用 try-catch。
- 事务范围: 保持事务尽可能短。
- 验证: 在开始事务之前验证数据。
- 嵌套事务: 理解特定于数据库的行为。
- 测试: 彻底测试回滚场景。
来源
本教程介绍了 PDO::rollBack 方法,并提供了实际示例,展示了事务回滚的各种必要场景。
作者
列出 所有 PHP PDO 函数。