PHP 使用PDO进行数据库事务

示例

数据库事务确保只有在每条语句成功后,一组数据更改才会被永久保留。可以捕获事务期间发生的任何查询或代码故障,然后您可以选择回滚尝试的更改。

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;
}