Tuyển dụng
Viettel IDC

Race Condition trong Database là gì? Nguyên nhân, ví dụ và giải pháp xử lý

10/07/2026

Hai request cùng lúc trừ tiền vào một tài khoản, hai đơn hàng cùng đặt mua sản phẩm cuối cùng trong kho. Kết quả trả về sai lệch dù nhìn qua logic ứng dụng "không có gì sai". Đây là biểu hiện điển hình của race condition trong database. Tuy khó debug nhưng hậu quả tài chính lại cực kỳ nghiêm trọng. 

Cùng Viettel IDC giải phẫu bản chất và cách xử lý triệt để bằng Locking và Isolation Level. 

Race Condition Trong Database là gì?

Hiện tượng Race condition trong database (Điều kiện tương tranh) xảy ra khi hai hoặc nhiều giao dịch (transaction) cùng truy cập và thao tác trên một tập dữ liệu tại cùng một thời điểm. Hậu quả là kết quả dữ liệu cuối cùng bị quyết định bởi thứ tự thực thi thực tế ở cấp độ vi mô (tiến trình nào chạy trước, tiến trình nào chạy sau), thay vì tuân theo logic nghiệp vụ đã được lập trình.

Nói một cách dễ hiểu: Cùng một đoạn code, cùng một dữ liệu đầu vào, nhưng hệ thống lại cho ra kết quả sai lệch hoặc không thể đoán trước chỉ vì sự chênh lệch tính bằng mili-giây giữa các lượt truy cập.

Race condition là "cơn ác mộng" đối với các hệ thống xử lý giao dịch cường độ cao như ngân hàng, sàn thương mại điện tử hay nền tảng đặt vé. Tại đây, mỗi dòng dữ liệu sai lệch (như bán vượt số lượng tồn kho, trừ tiền hai lần) đều quy đổi trực tiếp thành thiệt hại tài chính và uy tín.

Điều khiến lỗi này trở nên đặc biệt nguy hiểm là nó rất khó phát hiện. Trong môi trường kiểm thử (Test/Staging) với lượng truy cập đồng thời thấp, logic code trông có vẻ hoàn hảo. Lỗi này chỉ bộc lộ khi hệ thống bước ra môi trường thực tế và phải chịu tải với lượng truy cập đồng thời khổng lồ.

Race Condition Trong Database là gì?

Nhiều lập trình viên thường nhầm lẫn giữa hai rủi ro đồng thời là Race Condition và Deadlock . Dù cùng xuất phát từ việc kiểm soát transaction kém, nhưng bản chất của chúng hoàn toàn khác nhau:

- Race condition: Dữ liệu bị sai lệch, ghi đè do thứ tự truy cập không được kiểm soát chặt chẽ (hệ thống vẫn chạy nhưng kết quả sai).

- Deadlock (Bế tắc): Hai transaction tự khóa tài nguyên của nhau và chờ đợi lẫn nhau vô thời hạn, khiến các tiến trình bị treo cứng (hệ thống không thể chạy tiếp).

Cơ chế gây ra Race Condition trong Database

1. Mẫu hình "Read-Modify-Write" - nguồn cơn của mọi rắc rối

Phần lớn các lỗi Race condition trong database đều bắt nguồn từ một luồng thao tác kinh điển: Read-Modify-Write (Đọc - Sửa đổi - Ghi).

Quy trình diễn ra như sau: Ứng dụng đọc một giá trị hiện tại từ database (như số dư tài khoản, số lượng tồn kho) lên bộ nhớ (RAM), thực hiện tính toán logic ở tầng code, sau đó ghi đè giá trị mới trở lại database. Lỗ hổng chết người nằm ở "khoảng trễ" giữa bước Đọc và bước Ghi. Nếu có một transaction (giao dịch) khác cũng xen vào đọc cùng một dữ liệu trong khoảng thời gian này, cả hai sẽ cùng tính toán dựa trên một dữ liệu "cũ". Hậu quả là transaction nào hoàn tất việc ghi (Write) sau sẽ ghi đè và xóa sổ hoàn toàn kết quả của transaction trước đó.

2. Vai trò của Transaction và tính Isolation trong hệ ACID

Trong 4 trụ cột ACID (Atomicity, Consistency, Isolation, Durability) của cơ sở dữ liệu quan hệ, Isolation (Tính cô lập) chính là "chiếc khiên" kiểm soát mức độ ảnh hưởng lẫn nhau giữa các transaction chạy song song.

Mức độ Isolation càng được cấu hình chặt chẽ, khả năng xảy ra Race condition càng bị triệt tiêu. Tuy nhiên, bài toán đánh đổi ở đây là hiệu năng (Performance): Mức độ cô lập càng cao, database càng phải khóa (lock) nhiều tài nguyên và ép các tiến trình phải xếp hàng chờ đợi, làm giảm khả năng xử lý đồng thời (Concurrency) của hệ thống.

3. Các vấn đề đồng thời thường gặp

Race condition trong database không chỉ có một hình hài. Tùy thuộc vào kịch bản, nó sẽ biểu hiện qua 4 dạng lỗi đồng thời phổ biến sau đây:

Vấn đề 

Mô tả bản chất lỗi

Lost Update (Mất dữ liệu cập nhật)

Hai transaction cùng cập nhật một dòng dữ liệu. Kết quả của transaction chạy trước bị transaction chạy sau ghi đè và biến mất hoàn toàn khỏi hệ thống.

Dirty Read (Đọc dữ liệu rác)

Transaction A đọc được dữ liệu mà Transaction B vừa ghi nhưng chưa commit. Nếu B bị lỗi và rollback, dữ liệu A vừa đọc trở thành dữ liệu "ảo" không hề tồn tại.

Non-repeatable Read (Đọc không lặp lại)

Trong cùng một transaction, truy vấn một dòng dữ liệu 2 lần lại ra 2 kết quả khác nhau (do có một transaction khác đã xen vào commit thay đổi ở giữa khoảng thời gian đó).

Phantom Read (Đọc bóng ma)

Trong cùng một transaction, chạy một câu lệnh SELECT (với điều kiện WHERE) 2 lần lại trả về số lượng dòng khác nhau, do một transaction khác vừa INSERT thêm dữ liệu thỏa mãn điều kiện đó.

Ví dụ thực tế: Thảm họa "Overselling" trong Flash Sale

Race condition trong database không chỉ xuất hiện ở bài toán trừ tiền (Double spending) mà còn là ác mộng trong quản lý tồn kho — điển hình là sự cố bán vượt mức (Overselling) trong các chiến dịch Flash Sale.

Ví dụ: Một sàn thương mại điện tử tung deal sốc cho một chiếc iPhone, kho chỉ còn đúng 1 chiếc. Trong tích tắc, có hàng trăm yêu cầu (request) đặt mua đổ về.

Nếu code theo kiểu Read-Modify-Write thông thường:

- Hàng chục request cùng query vào database và thấy stock = 1.

- Logic ứng dụng kiểm tra 1 > 0 (Hợp lệ) và tiến hành lên đơn.

- Các request này lần lượt cập nhật stock = 0.
Kết quả: Kho từ 1 chiếc biến thành 0 chiếc, nhưng hệ thống lại báo đặt hàng thành công cho hàng chục khách hàng. Doanh nghiệp đối mặt với khủng hoảng truyền thông, buộc phải hủy đơn hoặc bù lỗ nặng nề.

Cách khắc phục: Giải pháp phổ biến và chắc chắn nhất là sử dụng Row-level Locking (Khóa cấp độ dòng). Khi request đầu tiên chạm vào dữ liệu tồn kho của chiếc iPhone đó, database sẽ "khóa" dòng dữ liệu này lại. Các request đến sau bắt buộc phải đứng chờ cho đến khi request đầu tiên xử lý xong (lúc này stock đã = 0). Khi đến lượt, các request sau sẽ đọc được stock = 0 và bị logic ứng dụng từ chối ngay lập tức.