数据库事务确保一组数据更改只有在每条语句都成功时才会永久生效。可以捕获事务期间的任何查询或代码故障,然后您可以选择回滚尝试的更改。
PDO 为开始、提交和回滚事务提供了简单的方法。
$pdo = new PDO(
$dsn,
$username,
$password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
try {
$statement = $pdo->prepare("UPDATE user SET name = :name");
$pdo->beginTransaction();
$statement->execute(["name"=>'Bob']);
$statement->execute(["name"=>'Joe']);
$pdo->commit();
}
catch (\Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollback();
// If we got here our two data updates are not in the database
}
throw $e;
}
在事务期间,所做的任何数据更改仅对活动连接可见。 SELECT
语句将返回更改后的更改,即使它们尚未提交到数据库。
注意:有关事务支持的详细信息,请参阅数据库供应商文档。有些系统根本不支持事务。有些支持嵌套事务,有些则不支持。
使用带有 PDO 的事务的实际示例
在下一节中演示了一个实际的现实世界示例,其中使用事务确保了数据库的一致性。
想象以下场景,假设您正在为电子商务网站构建购物车,并且您决定将订单保存在两个数据库表中。一个以字段orders
、order_id
、name
和address
命名。第二个以字段,和命名。第一个表包含订单的元数据,而第二个表包含已订购的实际产品。telephone
created_at
orders_products
order_id
product_id
quantity
向数据库中插入新订单
要将新订单插入数据库,您需要做两件事。首先,您需要INSERT
在表中创建一条新记录,该记录orders
将包含订单的元数据name
( 、address
等)。然后,您需要为订单中包含的每一种产品INSERT
在表中记录一条记录。orders_products
您可以通过执行类似于以下的操作来做到这一点:
// Insert the metadata of the order into the database
$preparedStatement = $db->prepare(
'INSERT INTO `orders` (`name`, `address`, `telephone`, `created_at`)
VALUES (:name, :address, :telephone, :created_at)'
);
$preparedStatement->execute([
'name' => $name,
'address' => $address,
'telephone' => $telephone,
'created_at' => time(),
]);
// Get the generated `order_id`
$orderId = $db->lastInsertId();
// Construct the query for inserting the products of the order
$insertProductsQuery = 'INSERT INTO `orders_products` (`order_id`, `product_id`, `quantity`) VALUES';
$count = 0;
foreach ( $products as $productId => $quantity ) {
$insertProductsQuery .= ' (:order_id' . $count . ', :product_id' . $count . ', :quantity' . $count . ')';
$insertProductsParams['order_id' . $count] = $orderId;
$insertProductsParams['product_id' . $count] = $productId;
$insertProductsParams['quantity' . $count] = $quantity;
++$count;
}
// Insert the products included in the order into the database
$preparedStatement = $db->prepare($insertProductsQuery);
$preparedStatement->execute($insertProductsParams);
这对于将新订单插入数据库非常有用,直到发生意外情况并且由于某种原因第二个INSERT
查询失败。如果发生这种情况,您最终将在orders
表中获得一个新订单,该订单将没有与之关联的产品。幸运的是,修复非常简单,您所要做的就是以单个数据库事务的形式进行查询。
使用事务将新订单插入数据库
要启动事务,PDO
您所要做的就是在beginTransaction
对数据库执行任何查询之前调用该方法。然后,您可以通过执行INSERT
和/或UPDATE
查询对数据进行任何更改。最后,您调用对象的commit
方法PDO
以使更改永久化。在您调用该commit
方法之前,您对数据所做的每一次更改都不是永久性的,只需调用对象的rollback
方法即可轻松恢复PDO
。
在下面的例子中演示了使用事务将新订单插入数据库,同时保证数据的一致性。如果两个查询之一失败,所有更改都将被还原。
// In this example we are using MySQL but this applies to any database that has support for transactions
$db = new PDO('mysql:host=' . $host . ';dbname=' . $dbname . ';charset=utf8', $username, $password);
// Make sure that PDO will throw an exception in case of error to make error handling easier
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
// From this point and until the transaction is being committed every change to the database can be reverted
$db->beginTransaction();
// Insert the metadata of the order into the database
$preparedStatement = $db->prepare(
'INSERT INTO `orders` (`order_id`, `name`, `address`, `created_at`)
VALUES (:name, :address, :telephone, :created_at)'
);
$preparedStatement->execute([
'name' => $name,
'address' => $address,
'telephone' => $telephone,
'created_at' => time(),
]);
// Get the generated `order_id`
$orderId = $db->lastInsertId();
// Construct the query for inserting the products of the order
$insertProductsQuery = 'INSERT INTO `orders_products` (`order_id`, `product_id`, `quantity`) VALUES';
$count = 0;
foreach ( $products as $productId => $quantity ) {
$insertProductsQuery .= ' (:order_id' . $count . ', :product_id' . $count . ', :quantity' . $count . ')';
$insertProductsParams['order_id' . $count] = $orderId;
$insertProductsParams['product_id' . $count] = $productId;
$insertProductsParams['quantity' . $count] = $quantity;
++$count;
}
// Insert the products included in the order into the database
$preparedStatement = $db->prepare($insertProductsQuery);
$preparedStatement->execute($insertProductsParams);
// Make the changes to the database permanent
$db->commit();
}
catch ( PDOException $e ) {
// Failed to insert the order into the database so we rollback any changes
$db->rollback();
throw $e;
}