使用 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();
        // If we got here our two data updates are not in the database
    }
    throw $e;
}

在事務期間,所做的任何資料更改僅對活動連線可見。SELECT 語句將返回已更改的更改,即使它們尚未提交到資料庫。

:有關事務支援的詳細資訊,請參閱資料庫供應商文有些系統根本不支援交易。有些支援巢狀事務,有些則不支援。

使用 PDO 進行事務的實際示例

在下一節中演示了一個實際的現實世界示例,其中使用事務確保資料庫的一致性。

想象一下以下場景,假設你正在為電子商務網站構建購物車,並且你決定將訂單儲存在兩個資料庫表中。一個名為 orders,欄位為 order_idnameaddresstelephonecreated_at。第二個名為 orders_products,欄位為 order_idproduct_idquantity。第一個表包含訂單的後設資料,第二個表包含已訂購的實際產品

將新訂單插入資料庫

要在資料庫中插入新訂單,你需要做兩件事。首先,你需要在 orders 表中建立一條新記錄,其中包含訂單的後設資料nameaddress 等)。然後,你需要將一條記錄傳送到 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 查詢,你可以對資料進行任何更改。最後,你呼叫 PDO 物件的 commit 方法使更改成為永久更改。在你呼叫 commit 方法之前,你對資料所做的每一項更改都不是永久性的,只需呼叫 PDO 物件的 rollback 方法即可輕鬆恢復。

在下面的示例中演示了使用事務將新訂單插入資料庫,同時確保資料的一致性。如果兩個查詢中的一個失敗,則將還原所有更改。

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