Transaction trong PHP và MySQL: Cách ghi dữ liệu an toàn không lo toang

Trong đời làm backend, có một nỗi đau rất quen: dữ liệu ghi được nửa chừng thì lỗi. Một bảng insert thành công, bảng kia fail. Kết quả? Database nát như tương bần. Đây chính là lúc transaction phát huy sức mạnh. Bài này nói thẳng, làm thật: cách dùng transaction trong PHP + MySQL để hoặc thành công tất cả, hoặc không ghi gì cả.

Transaction trong PHP và MySQL: Cách ghi dữ liệu an toàn không lo toang

Transaction là gì và vì sao rất quan trọng

Transaction là cơ chế cho phép bạn gom nhiều câu lệnh SQL thành một khối nguyên tử:

  • Tất cả cùng thành công → commit
  • Chỉ cần một câu lỗi → rollback toàn bộ

Không có transaction, database rất dễ rơi vào trạng thái nửa sống nửa chết.

Ví dụ thực tế rất dễ toang

Giả sử bạn cần:

  • Tạo đơn hàng
  • Trừ tồn kho sản phẩm

Nếu đơn hàng tạo xong nhưng trừ kho lỗi thì sao? Chúc mừng, dữ liệu đã sai vĩnh viễn.

Điều kiện để transaction hoạt động

  • MySQL dùng engine InnoDB
  • Không dùng MyISAM
  • Dùng PDO hoặc mysqli

Nếu bảng không phải InnoDB thì gọi transaction chỉ để… cho vui.

Cấu trúc transaction chuẩn

Transaction luôn có 3 bước:

  1. Begin transaction
  2. Thực hiện các câu SQL
  3. Commit hoặc rollback

Ví dụ transaction với PDO

<?php
$pdo = new PDO(
    'mysql:host=localhost;dbname=shop;charset=utf8mb4',
    'user',
    'password',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]
);

try {
    // 1. bắt đầu transaction
    $pdo->beginTransaction();

    // 2. tạo đơn hàng
    $stmt = $pdo->prepare("
        INSERT INTO orders (user_id, total_amount)
        VALUES (?, ?)
    ");
    $stmt->execute([123, 500000]);

    $orderId = $pdo->lastInsertId();

    // 3. trừ tồn kho
    $stmt = $pdo->prepare("
        UPDATE products
        SET stock = stock - 1
        WHERE id = ? AND stock > 0
    ");
    $stmt->execute([45]);

    if ($stmt->rowCount() !== 1) {
        throw new Exception('Không đủ tồn kho');
    }

    // 4. mọi thứ ok → commit
    $pdo->commit();

} catch (Throwable $e) {
    // có lỗi → rollback
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }

    die('Giao dịch thất bại: ' . $e->getMessage());
}

Vì sao phải dùng exception

Transaction chỉ thực sự hiệu quả khi:

  • Bất kỳ lỗi nào cũng ném exception
  • Không để code chạy tiếp trong trạng thái sai

Dùng PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION là bắt buộc, không thương lượng.

Kiểm tra logic không phải lỗi SQL

Nhiều trường hợp SQL không lỗi nhưng logic sai, ví dụ:

  • Không đủ tồn kho
  • Dữ liệu không hợp lệ

Lúc này, cần tự throw exception để rollback:

<?php
if ($stmt->rowCount() !== 1) {
    throw new Exception('Logic không hợp lệ');
}

Transaction với mysqli (nếu không dùng PDO)

<?php
$mysqli->begin_transaction();

try {
    $mysqli->query("INSERT INTO orders (...) VALUES (...)");
    $mysqli->query("UPDATE products SET stock = stock - 1 WHERE id = 45");

    $mysqli->commit();
} catch (Throwable $e) {
    $mysqli->rollback();
    throw $e;
}

Những sai lầm cần tránh

  • Quên commit
  • Catch lỗi nhưng không rollback
  • Dùng transaction với bảng MyISAM
  • Viết SQL ngoài transaction mà tưởng đang an toàn

Khi nào nên dùng transaction

  • Ghi dữ liệu vào nhiều bảng liên quan
  • Thanh toán, đơn hàng, điểm thưởng
  • Bất kỳ logic nào không được phép sai lệch

Kết luận

Transaction không phải thứ cao siêu, nhưng là ranh giới giữa backend chuyên nghiệp và code cầu may. Chỉ cần áp dụng đúng, bạn sẽ tránh được 90% thảm hoạ dữ liệu mà nhiều hệ thống phải trả giá bằng máu và nước mắt.

Nhớ kỹ: đã ghi nhiều thứ liên quan với nhau, thì hoặc sống cùng nhau, hoặc chết cùng nhau!

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...