数据库事务确保只有在每条语句成功后,一组数据更改才会被永久保留。可以捕获事务期间发生的任何查询或代码故障,然后您可以选择回滚尝试的更改。
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(); // 如果我们到达这里,我们的两个数据更新不在数据库中 } 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表中记录一条记录。
您可以通过执行类似以下操作来做到这一点:
// 将订单的元数据插入数据库 $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(), ]); // 获取生成的`order_id` $orderId = $db->lastInsertId(); // 构造查询以插入订单产品 $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; } // 将订单中包含的产品插入数据库 $preparedStatement = $db->prepare($insertProductsQuery); $preparedStatement->execute($insertProductsParams);
这对于将新订单插入数据库非常有用,直到发生意外情况并且由于某种原因第二个INSERT查询失败。如果发生这种情况,您将在orders表内获得一个新订单,该订单将没有任何关联的产品。幸运的是,此修复非常简单,您要做的就是以单个数据库事务的形式进行查询。
通过事务将新订单插入数据库
要使用PDO所有事务来启动事务,是beginTransaction在对数据库执行任何查询之前调用该方法。然后,您可以通过执行INSERT和/或UPDATE查询对数据进行任何更改。最后,您调用对象的commit方法PDO以使更改永久生效。在调用该commit方法之前,到目前为止,您对数据所做的每项更改都尚未永久生效,只需调用对象的rollback方法即可轻松地将其还原PDO。
在下面的示例中,演示了如何使用事务将新订单插入数据库,同时确保数据的一致性。如果两个查询之一失败,所有更改将被还原。
// 在此示例中,我们使用MySQL,但这适用于任何支持事务的数据库 $db = new PDO('mysql:host=' . $host . ';dbname=' . $dbname . ';charset=utf8', $username, $password); // 确保在发生错误的情况下PDO会引发异常,以使错误处理更加容易 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { // 从这一点开始,直到提交事务,对数据库的所有更改都可以还原 $db->beginTransaction(); // 将订单的元数据插入数据库 $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(), ]); // 获取生成的`order_id` $orderId = $db->lastInsertId(); // 构造查询以插入订单产品 $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; } // 将订单中包含的产品插入数据库 $preparedStatement = $db->prepare($insertProductsQuery); $preparedStatement->execute($insertProductsParams); // 永久更改数据库 $db->commit(); } catch ( PDOException $e ) { // 无法将订单插入数据库,因此我们回滚所有更改 $db->rollback(); throw $e; }