PHP PDO::beginTransaction 方法
最后修改于 2025 年 4 月 19 日
PDO::beginTransaction 方法在 PHP 中启动数据库事务。事务允许将多个数据库操作作为一个单一单元来执行。
基本定义
PDO::beginTransaction 会关闭数据库操作的自动提交模式。所有后续查询都将成为事务的一部分,直到提交或回滚。
语法:public PDO::beginTransaction(): bool
。成功时返回 true。如果事务已激活或驱动不支持事务,则抛出 PDOException。
简单事务示例
这演示了一个基本的事务,包括 beginTransaction、commit 和 rollBack。
<?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 不支持真正的嵌套事务,但这展示了如何模拟它们。
<?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(); }
这使用保存点来模拟嵌套事务。即使内部操作失败,外部事务也会提交。并非所有数据库都支持保存点。
带预处理语句的事务
这会将事务与预处理语句结合起来,以实现安全操作。
<?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 设置事务隔离级别。
<?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 之前设置隔离级别。
带错误处理的事务
这展示了事务上下文中的全面错误处理。
<?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(); }
这处理了数据库错误和业务逻辑故障。事务会为任何类型的错误进行回滚。自定义异常可以触发回滚。
跨多个表的事务
这演示了一个跨多个相关表的事务。
<?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(); }
这以原子方式创建订单项并更新库存。事务确保所有相关的数据库更改一起成功或失败。
带大型数据集的事务
这展示了如何在事务中高效地处理大量数据操作。
<?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); }
对于大型事务,请考虑定期提交。这可以防止超时和过多的内存使用。之后务必重新启用自动提交。
最佳实践
- 保持事务简短:最小化持续时间以减少锁定。
- 处理错误:始终在错误处理中实现回滚。
- 测试回滚:验证您的应用程序是否正确处理了故障。
- 检查支持:并非所有数据库都支持所有事务功能。
- 监控死锁:准备好在死锁时重试事务。
来源
本教程涵盖了 PDO::beginTransaction
方法,并提供了实际示例,展示了 PHP 数据库操作中的不同事务场景。
作者
列出 所有 PHP PDO 函数。