本文目录导读:

在 PHP 中,保存点(Savepoint)允许你在事务中设置标记点,以便在出错时可以部分回滚到指定位置,而不是回滚整个事务,以下是完整的实现方法:
MySQLi 实现保存点
<?php
$mysqli = new mysqli("localhost", "user", "password", "database");
if ($mysqli->connect_error) {
die("连接失败: " . $mysqli->connect_error);
}
$mysqli->autocommit(FALSE);
try {
// 开启事务
$mysqli->begin_transaction();
// 第一步操作
$mysqli->query("INSERT INTO users (name, email) VALUES ('张三', 'zhangsan@example.com')");
$user_id = $mysqli->insert_id;
// 设置保存点
$mysqli->query("SAVEPOINT sp_user_created");
// 第二步操作 - 这里可能会出错
$mysqli->query("INSERT INTO orders (user_id, amount) VALUES ($user_id, 100)");
$order_id = $mysqli->insert_id;
// 设置另一个保存点
$mysqli->query("SAVEPOINT sp_order_created");
// 第三步操作 - 模拟一个错误
$result = $mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE user_id = $user_id");
if (!$result) {
// 回滚到 sp_order_created 保存点
$mysqli->query("ROLLBACK TO SAVEPOINT sp_order_created");
echo "回滚到订单创建后的状态<br>";
}
// 检查数据状态
echo "用户ID: " . $user_id . "<br>";
echo "订单ID: " . $order_id . "<br>";
// 提交事务
$mysqli->commit();
echo "事务成功提交";
} catch (Exception $e) {
// 完全回滚
$mysqli->rollback();
echo "事务回滚: " . $e->getMessage();
}
$mysqli->close();
?>
PDO 实现保存点
<?php
try {
$pdo = new PDO(
"mysql:host=localhost;dbname=database",
"user",
"password",
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
// 开启事务
$pdo->beginTransaction();
// 第一步操作
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute(['李四', 'lisi@example.com']);
$user_id = $pdo->lastInsertId();
// 创建保存点
$pdo->exec("SAVEPOINT sp_user_created");
try {
// 第二步操作
$stmt = $pdo->prepare("INSERT INTO orders (user_id, amount) VALUES (?, ?)");
$stmt->execute([$user_id, 200]);
$order_id = $pdo->lastInsertId();
// 创建第二个保存点
$pdo->exec("SAVEPOINT sp_order_created");
// 第三步操作 - 可能会失败
$stmt = $pdo->prepare("UPDATE inventory SET stock = stock - 1 WHERE product_id = ?");
$success = $stmt->execute([1]);
if (!$success) {
// 回滚到订单保存点
$pdo->exec("ROLLBACK TO SAVEPOINT sp_order_created");
echo "回滚到订单创建点<br>";
}
// 提交事务
$pdo->commit();
echo "事务提交成功";
} catch (Exception $e) {
// 回滚到用户创建保存点
$pdo->exec("ROLLBACK TO SAVEPOINT sp_user_created");
echo "回滚到用户创建点: " . $e->getMessage() . "<br>";
// 可以继续执行其他操作或提交
$pdo->commit();
}
} catch (Exception $e) {
// 完全回滚
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
echo "错误: " . $e->getMessage();
}
?>
完整的业务场景示例
<?php
class TransactionManager {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/**
* 复杂订单处理事务
*/
public function processOrder($userId, $products) {
try {
// 开始事务
$this->pdo->beginTransaction();
// 1. 创建订单
$orderId = $this->createOrder($userId);
// 设置保存点
$this->pdo->exec("SAVEPOINT sp_order");
// 2. 添加订单商品
$this->addOrderItems($orderId, $products);
// 设置保存点
$this->pdo->exec("SAVEPOINT sp_items");
// 3. 更新库存(可能失败)
try {
$this->updateInventory($products);
} catch (Exception $e) {
// 库存更新失败,回滚到添加商品前
$this->pdo->exec("ROLLBACK TO SAVEPOINT sp_items");
echo "库存更新失败,已回滚商品添加<br>";
// 继续处理,不阻塞整个事务
$this->pdo->exec("SAVEPOINT sp_after_inventory_failure");
}
// 4. 处理支付(可能失败)
try {
$this->processPayment($orderId);
} catch (Exception $e) {
// 支付失败,回滚到订单创建后
$this->pdo->exec("ROLLBACK TO SAVEPOINT sp_order");
// 添加订单失败记录
$this->logFailedOrder($userId, $e->getMessage());
$this->pdo->commit();
return ['success' => false, 'message' => '支付失败', 'orderId' => $orderId];
}
// 提交事务
$this->pdo->commit();
return ['success' => true, 'message' => '订单处理成功', 'orderId' => $orderId];
} catch (Exception $e) {
// 完全回滚
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
return ['success' => false, 'message' => $e->getMessage()];
}
}
private function createOrder($userId) {
$stmt = $this->pdo->prepare("INSERT INTO orders (user_id, status) VALUES (?, 'pending')");
$stmt->execute([$userId]);
return $this->pdo->lastInsertId();
}
private function addOrderItems($orderId, $products) {
$stmt = $this->pdo->prepare(
"INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)"
);
foreach ($products as $product) {
$stmt->execute([$orderId, $product['product_id'], $product['quantity']]);
}
}
private function updateInventory($products) {
$stmt = $this->pdo->prepare(
"UPDATE products SET stock = stock - ? WHERE product_id = ? AND stock >= ?"
);
foreach ($products as $product) {
$stmt->execute([
$product['quantity'],
$product['product_id'],
$product['quantity']
]);
if ($stmt->rowCount() === 0) {
throw new Exception("库存不足: 产品ID " . $product['product_id']);
}
}
}
private function processPayment($orderId) {
// 模拟支付处理
if (rand(0, 10) > 7) { // 30% 失败率
throw new Exception("支付处理失败");
}
$stmt = $this->pdo->prepare("UPDATE orders SET status = 'paid' WHERE id = ?");
$stmt->execute([$orderId]);
}
private function logFailedOrder($userId, $error) {
$stmt = $this->pdo->prepare(
"INSERT INTO order_failures (user_id, error_message) VALUES (?, ?)"
);
$stmt->execute([$userId, $error]);
}
}
// 使用示例
$pdo = new PDO("mysql:host=localhost;dbname=database", "user", "password");
$manager = new TransactionManager($pdo);
$result = $manager->processOrder(1, [
['product_id' => 1, 'quantity' => 2],
['product_id' => 2, 'quantity' => 1]
]);
print_r($result);
?>
保存点操作的注意事项
<?php
// 检查是否支持保存点
function checkSavepointSupport($pdo) {
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
switch ($driver) {
case 'mysql':
// MySQL 5.0+ 支持
return true;
case 'pgsql':
// PostgreSQL 8.0+ 支持
return true;
case 'sqlite':
// SQLite 3.0+ 支持
return true;
case 'sqlsrv':
// SQL Server 2005+ 支持
return true;
default:
return false;
}
}
// 封装保存点操作类
class SavepointHelper {
private $pdo;
private $savepoints = [];
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function create($name) {
$this->pdo->exec("SAVEPOINT $name");
array_push($this->savepoints, $name);
return true;
}
public function rollbackTo($name) {
if (in_array($name, $this->savepoints)) {
$this->pdo->exec("ROLLBACK TO SAVEPOINT $name");
// 移除该保存点之后的所有保存点
while (end($this->savepoints) !== $name) {
array_pop($this->savepoints);
}
return true;
}
return false;
}
public function release($name) {
$this->pdo->exec("RELEASE SAVEPOINT $name");
$key = array_search($name, $this->savepoints);
if ($key !== false) {
unset($this->savepoints[$key]);
}
return true;
}
public function getActiveSavepoints() {
return $this->savepoints;
}
}
// 使用封装类
$helper = new SavepointHelper($pdo);
$pdo->beginTransaction();
$helper->create('step1');
// 执行操作...
$helper->create('step2');
// 执行操作...
// 如果需要回滚
$helper->rollbackTo('step1');
$pdo->commit();
?>
最佳实践建议
<?php
class TransactionWithSavepoints {
private $pdo;
private $savepointCounter = 0;
/**
* 执行带保存点的事务
*/
public function executeTransaction(callable $callback) {
$this->pdo->beginTransaction();
try {
// 给回调传入保存点管理对象
$result = $callback($this);
// 提交事务
$this->pdo->commit();
return $result;
} catch (Exception $e) {
// 捕获所有异常,执行完整回滚
$this->pdo->rollBack();
throw $e;
}
}
/**
* 创建保存点
*/
public function createSavepoint() {
$name = 'sp_' . (++$this->savepointCounter);
$this->pdo->exec("SAVEPOINT $name");
return $name;
}
/**
* 回滚到特定保存点
*/
public function rollbackToSavepoint($name) {
$this->pdo->exec("ROLLBACK TO SAVEPOINT $name");
// 重置保存点计数器(可选)
$this->savepointCounter = (int)substr($name, 3);
}
/**
* 释放保存点
*/
public function releaseSavepoint($name) {
$this->pdo->exec("RELEASE SAVEPOINT $name");
}
}
// 使用示例
$transaction = new TransactionWithSavepoints($pdo);
$result = $transaction->executeTransaction(function($tx) {
// 执行第一个操作
// ...
// 创建保存点
$sp = $tx->createSavepoint();
// 执行可能会失败的操作
try {
// 进行风险操作
// ...
} catch (Exception $e) {
// 回滚到保存点
$tx->rollbackToSavepoint($sp);
}
// 继续其他操作
// ...
return 'transactions complete';
});
echo $result;
?>
-
保存点只能在事务中使用:必须在
BEGIN TRANSACTION之后才能创建保存点 -
保存点命名规范:使用有意义的名称,如
sp_user_created,便于维护 -
回滚范围:
ROLLBACK TO SAVEPOINT只回滚到指定点之后的操作,之前的操作保持不变 -
内存管理:及时释放不需要的保存点,避免过度使用
-
错误处理:始终使用 try-catch 捕获异常,确保在错误时能正确恢复
-
数据库支持:确保数据库版本支持保存点功能(MySQL 5.0+,PostgreSQL 8.0+ 等)
-
性能考虑:保存点会占用资源,不要过度使用,在必要的情况下才使用
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播