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 函数。