Transaction Isolation Levels — Hiểu đúng để không mất dữ liệu trong hệ thống concurrent

8 phút đọc

Transaction Isolation Levels — Hiểu đúng để không mất dữ liệu trong hệ thống concurrent

Khi nhiều user cùng thao tác trên database, chuyện gì xảy ra nếu 2 transaction đọc/ghi cùng 1 dòng dữ liệu? Câu trả lời phụ thuộc vào Isolation Level — mức độ "cách ly" mà database cung cấp giữa các transaction chạy song song.

Bài viết này sẽ giúp bạn:

  • Hiểu bản chất từng isolation level qua analogy trực quan
  • Nhận diện 3 loại anomaly nguy hiểm: Dirty Read, Non-repeatable Read, Phantom Read
  • Biết cách set isolation level trong Laravel
  • Chọn đúng level cho từng use case thực tế

1. Isolation Level là gì?

Isolation Level quyết định mức độ nhìn thấy dữ liệu giữa các concurrent transactions. Level càng cao → transaction càng "riêng tư" → càng an toàn, nhưng đánh đổi bằng performance.

SQL Standard định nghĩa 4 mức:

Isolation Level Dirty Read Non-repeatable Read Phantom Read Performance
READ UNCOMMITTED ❌ Có thể xảy ra ❌ Có thể xảy ra ❌ Có thể xảy ra ⚡⚡⚡⚡ Nhanh nhất
READ COMMITTED ✅ Ngăn được ❌ Có thể xảy ra ❌ Có thể xảy ra ⚡⚡⚡
REPEATABLE READ (default MySQL) ✅ Ngăn được ✅ Ngăn được ❌ Có thể xảy ra ⚡⚡
SERIALIZABLE ✅ Ngăn được ✅ Ngăn được ✅ Ngăn được ⚡ Chậm nhất

2. Analogy: Khách sạn & Phòng làm việc

Hãy tưởng tượng database là một khách sạn, mỗi transaction là một khách hàng vào phòng làm việc chung để đọc/sửa tài liệu trên bàn.

READ UNCOMMITTED — Phòng không có cửa

Bạn đang viết dở trên bảng trắng, chưa hoàn thành. Ai đi ngang cũng nhìn thấy và chụp lại. Nếu bạn xóa đi viết lại (rollback) → người kia đã mang đi dữ liệu sai.

Dirty Read xảy ra.

READ COMMITTED — Phòng có rèm, chỉ mở khi "Done"

Người khác chỉ thấy bảng trắng sau khi bạn ký tên xác nhận (commit). Nhưng nếu họ nhìn 2 lần cách nhau vài giây, bảng có thể đã bị sửa xong bởi người khác → kết quả khác.

→ Ngăn Dirty Read, nhưng Non-repeatable Read vẫn xảy ra.

REPEATABLE READ — Chụp snapshot lúc vào phòng

Khi bạn bước vào phòng, hệ thống chụp snapshot toàn bộ dữ liệu. Suốt phiên, bạn luôn nhìn thấy đúng bức ảnh ban đầu. Nhưng nếu có người thêm row mới → snapshot không bắt kịp.

→ Ngăn Dirty Read + Non-repeatable Read, nhưng Phantom Read vẫn có thể xảy ra.

SERIALIZABLE — Phòng VIP, vào từng người một

Mỗi lần chỉ 1 người được vào phòng. An toàn tuyệt đối — không ai chen ngang, không ai nhìn trộm. Nhưng chậm nhất.

→ Ngăn được tất cả anomalies.


3. Ba loại Anomaly nguy hiểm

3.1 Dirty Read — Đọc dữ liệu chưa commit

Transaction A đọc dữ liệu mà transaction B đã sửa nhưng chưa commit. Nếu B rollback → A đã quyết định dựa trên dữ liệu không bao giờ tồn tại.

-- Transaction B (chuyển tiền):          | Transaction A (kiểm tra số dư):
BEGIN;                                   |
  UPDATE accounts                        |
    SET balance = balance - 500           |
    WHERE id = 1;                        |
  -- balance = 500 (chưa commit)         |
                                         | BEGIN;
                                         |   SELECT balance FROM accounts
                                         |     WHERE id = 1;
                                         |   -- Đọc được: 500 ← DỮ LIỆU BẨN!
                                         | COMMIT;
ROLLBACK;                                |
-- balance thực sự vẫn = 1000            |

Ngăn bằng: READ COMMITTED trở lên.

3.2 Non-repeatable Read — Cùng query, khác kết quả

Trong cùng 1 transaction, đọc cùng 1 row 2 lần nhưng kết quả khác nhau vì transaction khác đã UPDATE và commit xen giữa.

-- Transaction A (tạo báo cáo):          | Transaction B (admin sửa giá):
BEGIN;                                   |
  SELECT price FROM products             |
    WHERE id = 1;                        |
  -- Lần 1: price = 100                  |
                                         | BEGIN;
                                         |   UPDATE products SET price = 200
                                         |     WHERE id = 1;
                                         | COMMIT;
  SELECT price FROM products             |
    WHERE id = 1;                        |
  -- Lần 2: price = 200 ← KHÁC!         |
COMMIT;                                  |

Ngăn bằng: REPEATABLE READ trở lên.

3.3 Phantom Read — Row "ma" xuất hiện

Query theo cùng điều kiện WHERE 2 lần nhưng số lượng row khác nhau vì transaction khác đã INSERT/DELETE xen giữa.

-- Transaction A (kiểm tra phòng):       | Transaction B (đặt phòng):
BEGIN;                                   |
  SELECT COUNT(*) FROM rooms             |
    WHERE status = 'available'           |
      AND date = '2026-04-01';           |
  -- Lần 1: COUNT = 3                    |
                                         | BEGIN;
                                         |   INSERT INTO rooms (status, date)
                                         |     VALUES ('available','2026-04-01');
                                         | COMMIT;
  SELECT COUNT(*) FROM rooms             |
    WHERE status = 'available'           |
      AND date = '2026-04-01';           |
  -- Lần 2: COUNT = 4 ← ROW MA!         |
COMMIT;                                  |

Ngăn bằng: SERIALIZABLE.


4. Cách set Isolation Level trong Laravel

use Illuminate\Support\Facades\DB;

// Set isolation level trước khi bắt đầu transaction
DB::connection()->statement(
    'SET TRANSACTION ISOLATION LEVEL READ COMMITTED'
);

DB::transaction(function () {
    // Logic xử lý trong transaction
    $balance = DB::table('accounts')
        ->where('id', 1)
        ->value('balance');

    DB::table('accounts')
        ->where('id', 1)
        ->update(['balance' => $balance - 500]);
});

Các level có thể set:

// ⚠️ Nguy hiểm — có thể đọc uncommitted data
'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED'

// Ngăn dirty reads — phổ biến trong reporting
'SET TRANSACTION ISOLATION LEVEL READ COMMITTED'

// MySQL default — cân bằng tốt cho CRUD
'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ'

// Mạnh nhất nhưng chậm nhất — dùng cho giao dịch tài chính nhạy cảm
'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'

5. Chọn Isolation Level nào cho dự án thực tế?

Use case Isolation Level Lý do
CRUD app thông thường (e-commerce, booking) REPEATABLE READ Default MySQL, đủ an toàn cho 90% trường hợp
Reporting / Analytics dashboard READ COMMITTED Cần đọc dữ liệu mới nhất, chấp nhận non-repeatable read
Chuyển tiền ngân hàng, audit tài chính SERIALIZABLE Yêu cầu consistency tuyệt đối, chấp nhận hy sinh performance
Debug / Monitoring nhanh READ UNCOMMITTED Chỉ dùng trong development, không bao giờ dùng production

Tổng kết

Isolation Level là một trong những khái niệm cốt lõi mà bất kỳ backend developer nào làm việc với relational database đều cần nắm vững. Quy tắc đơn giản:

  1. Đừng thay đổi default (REPEATABLE READ) trừ khi bạn có lý do rõ ràng.
  2. Hiểu trade-off: an toàn hơn = chậm hơn. Chọn mức vừa đủ cho use case.
  3. Luôn test với concurrent requests trước khi deploy — isolation bug chỉ lộ ra dưới tải thực.

Bài viết liên quan

Đang cập nhật...