ZetCode

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 的最佳实践

来源

PHP PDO::rollBack 文档

本教程介绍了 PDO::rollBack 方法,并提供了实际示例,展示了事务回滚的各种必要场景。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。我自 2007 年以来一直撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在教学编程方面拥有十多年的经验。

列出 所有 PHP PDO 函数