ZetCode

PHP PDO::beginTransaction 方法

最后修改于 2025 年 4 月 19 日

PDO::beginTransaction 方法在 PHP 中启动数据库事务。事务允许将多个数据库操作作为一个单一单元来执行。

基本定义

PDO::beginTransaction 会关闭数据库操作的自动提交模式。所有后续查询都将成为事务的一部分,直到提交或回滚。

语法:public PDO::beginTransaction(): bool。成功时返回 true。如果事务已激活或驱动不支持事务,则抛出 PDOException。

简单事务示例

这演示了一个基本的事务,包括 beginTransaction、commit 和 rollBack。

basic_transaction.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 orders (product, amount) VALUES ('Laptop', 1)");
    $pdo->exec("UPDATE inventory SET stock = stock - 1 WHERE product = 'Laptop'");
    
    $pdo->commit();
    echo "Transaction completed successfully";
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Transaction failed: " . $e->getMessage();
}

这展示了一个完整的事务流程。订单插入和库存更新是原子的。如果其中任何一个失败,两个操作都将被回滚。

嵌套事务示例

PDO 不支持真正的嵌套事务,但这展示了如何模拟它们。

nested_transactions.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(); // Outer transaction
    
    try {
        $pdo->exec("INSERT INTO logs (message) VALUES ('Starting operation')");
        
        // Simulate nested transaction with savepoints
        $pdo->exec("SAVEPOINT point1");
        
        $pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
        $pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
        
        $pdo->exec("RELEASE SAVEPOINT point1");
        
        $pdo->commit();
        echo "All operations completed";
    } catch (PDOException $e) {
        $pdo->exec("ROLLBACK TO SAVEPOINT point1");
        $pdo->commit(); // Commit the outer transaction
        echo "Partial operation completed with error: " . $e->getMessage();
    }
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Operation failed: " . $e->getMessage();
}

这使用保存点来模拟嵌套事务。即使内部操作失败,外部事务也会提交。并非所有数据库都支持保存点。

带预处理语句的事务

这会将事务与预处理语句结合起来,以实现安全操作。

transaction_prepared.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();
    
    $stmt1 = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
    $stmt1->execute(['John Doe', 'john@example.com']);
    
    $userId = $pdo->lastInsertId();
    
    $stmt2 = $pdo->prepare("INSERT INTO user_roles (user_id, role) VALUES (?, ?)");
    $stmt2->execute([$userId, 'member']);
    
    $pdo->commit();
    echo "User created with role successfully";
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "User creation failed: " . $pdo->errorInfo()[2];
}

这在事务中创建用户并分配角色。预处理语句可防止 SQL 注入。事务确保两个操作都成功或失败。

事务隔离级别

这演示了如何使用 beginTransaction 设置事务隔离级别。

isolation_levels.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);
    
    // Set isolation level before beginning transaction
    $pdo->exec("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
    
    $pdo->beginTransaction();
    
    // Perform operations that need consistent reads
    $stmt = $pdo->query("SELECT * FROM accounts WHERE id = 1");
    $account = $stmt->fetch(PDO::FETCH_ASSOC);
    
    // Update based on the read
    $pdo->exec("UPDATE accounts SET balance = balance - 50 WHERE id = 1");
    
    $pdo->commit();
    echo "Balance updated based on consistent read";
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Transaction failed: " . $e->getMessage();
}

隔离级别控制事务对其他事务的可见性。READ COMMITTED 可防止脏读。在 beginTransaction 之前设置隔离级别。

带错误处理的事务

这展示了事务上下文中的全面错误处理。

transaction_errors.php
<?php

declare(strict_types=1);

$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

try {
    $pdo->beginTransaction();
    
    $pdo->exec("UPDATE products SET stock = stock - 1 WHERE id = 101");
    
    if ($pdo->exec("UPDATE orders SET status = 'shipped' WHERE id = 5001") === 0) {
        throw new Exception("No order found with ID 5001");
    }
    
    $pdo->commit();
    echo "Order processed successfully";
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Database error: " . $e->getMessage();
} catch (Exception $e) {
    $pdo->rollBack();
    echo "Business logic error: " . $e->getMessage();
}

这处理了数据库错误和业务逻辑故障。事务会为任何类型的错误进行回滚。自定义异常可以触发回滚。

跨多个表的事务

这演示了一个跨多个相关表的事务。

multi_table_transaction.php
<?php

declare(strict_types=1);

try {
    $pdo = new PDO('mysql:host=localhost;dbname=ecommerce', 'user', 'password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    $pdo->beginTransaction();
    
    // Create order
    $stmt = $pdo->prepare("INSERT INTO orders (customer_id, total) VALUES (?, ?)");
    $stmt->execute([1, 199.99]);
    $orderId = $pdo->lastInsertId();
    
    // Add order items
    $items = [
        ['product_id' => 101, 'quantity' => 1, 'price' => 99.99],
        ['product_id' => 205, 'quantity' => 2, 'price' => 50.00]
    ];
    
    $stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)");
    
    foreach ($items as $item) {
        $stmt->execute([$orderId, $item['product_id'], $item['quantity'], $item['price']]);
    }
    
    // Update inventory
    foreach ($items as $item) {
        $pdo->exec("UPDATE inventory SET stock = stock - {$item['quantity']} WHERE product_id = {$item['product_id']}");
    }
    
    $pdo->commit();
    echo "Order #$orderId processed successfully";
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Order processing failed: " . $e->getMessage();
}

这以原子方式创建订单项并更新库存。事务确保所有相关的数据库更改一起成功或失败。

带大型数据集的事务

这展示了如何在事务中高效地处理大量数据操作。

large_data_transaction.php
<?php

declare(strict_types=1);

try {
    $pdo = new PDO('mysql:host=localhost;dbname=analytics', 'user', 'password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    // Disable autocommit for better performance with large transactions
    $pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);
    
    $pdo->beginTransaction();
    
    // Clear old data
    $pdo->exec("TRUNCATE TABLE monthly_report");
    
    // Process and insert large dataset
    $stmt = $pdo->prepare("INSERT INTO monthly_report (metric, value, date) VALUES (?, ?, ?)");
    
    $metrics = ['visitors', 'conversions', 'revenue'];
    $dates = new DatePeriod(new DateTime('first day of last month'), 
        new DateInterval('P1D'), new DateTime('first day of this month'));
    
    foreach ($dates as $date) {
        foreach ($metrics as $metric) {
            $value = rand(100, 1000); // Simulate data
            $stmt->execute([$metric, $value, $date->format('Y-m-d')]);
        }
        
        // Commit periodically for large transactions
        if ($date->format('d') % 7 === 0) {
            $pdo->commit();
            $pdo->beginTransaction();
        }
    }
    
    $pdo->commit();
    echo "Monthly report generated successfully";
} catch (PDOException $e) {
    $pdo->rollBack();
    echo "Report generation failed: " . $e->getMessage();
} finally {
    // Re-enable autocommit
    $pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
}

对于大型事务,请考虑定期提交。这可以防止超时和过多的内存使用。之后务必重新启用自动提交。

最佳实践

来源

PHP PDO::beginTransaction 文档

本教程涵盖了 PDO::beginTransaction 方法,并提供了实际示例,展示了 PHP 数据库操作中的不同事务场景。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。

列出 所有 PHP PDO 函数