
Trong thế giới cơ sở dữ liệu, "transaction" là một khái niệm cốt lõi nhưng thường bị hiểu nhầm. Mình bắt gặp rất nhiều cuộc trò chuyện về transaction trong Database, nhưng đa phần đều là những quan niệm sai lầm kiểu:
"Database A có transaction còn Database B không có transaction."
"Transaction phải bao gồm 4 yếu tố ACID."
"Chán cái bọn NoSQL mãi không chịu hỗ trợ transaction!"
Qua bài này, mình sẽ "vạch trần" một chút sự thật về Transaction để anh em hiểu rõ hơn. Lợi ích trước mắt là sẽ không bị bối rối giữa các thuật ngữ khi đọc tài liệu của bọn nước ngoài, còn lợi ích lâu dài là... tránh bị lừa bởi mấy chiêu trò marketing bánh vẽ.
Vậy Transaction là gì?#
Transaction chính là cái chữ T trong OLTP (Online Transaction Processing). OLTP là hệ thống phục vụ các hoạt động tương tác trực tiếp của ứng dụng, như thanh toán, đặt hàng, đăng bài, cập nhật profile. Còn OLAP (Online Analytical Processing) thì lo về mảng phân tích, báo cáo, xấp xỉ là đủ.
Về mặt kỹ thuật, transaction là đơn vị công việc nhỏ nhất của cơ sở dữ liệu, có thể là một thao tác đơn lẻ như SELECT, INSERT, hoặc một nhóm nhiều thao tác được thực hiện như một khối. Cái tên "transaction" (giao dịch) xuất phát từ lĩnh vực tài chính ngân hàng - nơi nó ra đời.
Theo thời gian, từ này bị "biến tướng" và được marketing đánh bóng khá nhiều bởi những người... không hiểu kỹ thuật. Cho nên, anh em cần tỉnh táo: Transaction, về bản chất, không đồng nghĩa với việc đi kèm với ACID.
ACID là gì, và màn ảo thuật "bánh vẽ" của nó#
ACID là viết tắt của Atomicity, Consistency, Isolation, Durability. Đây là bộ tiêu chuẩn về khả năng chịu lỗi, được đề xuất từ năm 1983. Tiêu chuẩn thì có đấy, nhưng mỗi database lại implement một kiểu, với nhiều mức độ khác nhau. Thường thì chỉ có mức cao nhất là Serializable Isolation mới đạt chuẩn 100%, còn mặc định họ hay set ở mức thấp hơn để đánh đổi lấy hiệu năng.
Đây chính là mảnh đất màu mỡ cho trò "bánh vẽ" marketing:
- Loại bánh vẽ thứ nhất: “Database Y có hỗ trợ ACID!”
Mặc định nhà phát triển sẽ cấu hình sẵn ở mức thấp, và dữ liệu sẽ không đảm bảo được chính xác như những gì mình kỳ vọng.
- Loại bánh vẽ thứ hai: "Database Z có hỗ trợ ACID!"
Đây chính là bánh vẽ trước đây mà MongoDB ngày xưa từng dùng.
Bài học: Luôn đọc kỹ whitepaper hoặc documentation để xem họ implement thế nào, và tự benchmark thử (như cái benchmark về Isolation levels của Martin Kleppmann chẳng hạn: https://github.com/ept/hermitage).
Atomicity (Tính nguyên tử) - "All or Nothing"#
Chữ A này thực chất nên hiểu là Abortability (khả năng hủy bỏ) thì đúng hơn. Nó đảm bảo với người dùng rằng một transaction hoặc là thực thi toàn bộ, hoặc là không thực thi gì cả, không có trạng thái "nửa vời".
Ví dụ: Bạn chuyển tiền từ tài khoản A sang B. Atomicity đảm bảo rằng:
Thành công: Tiền được trừ ở A VÀ cộng vào B.
Thất bại: Tiền KHÔNG bị trừ ở A VÀ cũng KHÔNG được cộng vào B.
Nếu gặp lỗi giữa chừng (ví dụ: mất kết nối sau khi đã trừ tiền ở A), hệ thống phải rollback (khôi phục) mọi thay đổi về trạng thái ban đầu. Điều này giúp lập trình viên có thể yên tâm retry khi gặp lỗi mà không sợ gây ra dữ liệu bị duplicate hoặc không chính xác.

Bạn có nhận ra rằng: quá trình cài đặt phần mềm chính là 1 transaction không?
Consistency (Tính nhất quán) - Không phải "nhất quán" theo nghĩa CAP#
Chữ C trong ACID KHÔNG PHẢI là Consistency trong CAP Theorem (vốn nói về tính nhất quán giữa các node trong hệ thống phân tán). Ở đây, Consistency có nghĩa là dữ liệu phải tuân thủ mọi ràng buộc (constraints) đã định nghĩa từ trước của database.
Ví dụ về ràng buộc:
Khóa chính (Primary Key), khóa ngoại (Foreign Key).
Unique constraint: Tên người dùng phải là duy nhất.
Check constraint: Số dư tài khoản không được âm, độ tuổi phải > 0.
Lưu ý quan trọng: Database chỉ có thể đảm bảo được những ràng buộc mà nó được thiết lập. Còn những "rule oái ăm" từ nghiệp vụ (business logic) thì hoàn toàn phụ thuộc vào code của bạn. Hãy nhớ validate cẩn thận trước khi insert/update dữ liệu, một khi mà có bug thì toang đấy, không có Database nào có thể cứu được mình đâu.
Ví dụ điển hình về lỗi Consistency do code:
Chuyển 300$ từ A sang B.
Code quên kiểm tra xem A có đủ 300$ không → Vi phạm ràng buộc "số dư >= 0".
Code chỉ cộng tiền cho B mà quên trừ tiền của A → Vi phạm tính toàn vẹn của nghiệp vụ.
Database không thể tự biết được rule "tổng số tiền phải được bảo toàn" nếu bạn không mã hóa nó thành transaction đúng cách (Atomicity sẽ giúp phần này). Consistency trong ACID là trách nhiệm chung: Database đảm bảo các ràng buộc cơ sở, còn lập trình viên phải đảm bảo tính đúng đắn của nghiệp vụ thông qua các transaction được thiết kế chuẩn chỉnh.
Isolation (Sự cô lập) - Khi các transaction chạy song song không "đạp chân" nhau#
Isolation giải quyết vấn đề cốt lõi: Khi nhiều transaction chạy đồng thời, chúng không được phép nhìn thấy trạng thái "dở dang" của nhau, và không được can thiệp xấu vào kết quả của nhau.
Ví dụ dễ hiểu về Isolation lỗi:
Hai giao dịch rút tiền cùng lúc từ một tài khoản có 100$.
Giao dịch A đọc số dư: 100$.
Giao dịch B cũng đọc số dư: 100$.
A trừ 10$, ghi 90$ vào tài khoản.
B trừ 10$, ghi 90$ vào tài khoản (đè lên kết quả của A).
Kết quả sai: Số dư cuối là 90$ thay vì 80$. Isolation (ở mức Repeatable Read trở lên) sẽ ngăn B đọc dữ liệu cho đến khi A hoàn tất, hoặc phát hiện xung đột và buộc một trong hai phải thử lại.
Các vấn đề Isolation ngăn chặn:
Dirty Read: Đọc dữ liệu từ một transaction chưa commit (có thể sau đó bị rollback).
Non-repeatable Read: Trong cùng một transaction, hai lần đọc cùng một hàng nhưng nhận được giá trị khác nhau vì có transaction khác đã cập nhật giữa chừng.
Phantom Read: Trong cùng một transaction, hai lần thực hiện cùng một query
SELECTvới điều kiện như nhau nhưng nhận về số lượng hàng khác nhau, vì có transaction khác đã chèn/xóa dữ liệu phù hợp với điều kiện đó.

Mức độ Isolation (từ thấp đến cao):
Read Uncommitted: Cho phép Dirty Read — hầu như không dùng trong thực tế.
Read Committed: Chỉ đọc dữ liệu đã commit (mức mặc định của nhiều database).
Repeatable Read: Đảm bảo trong một transaction, các lần đọc lặp lại cùng một hàng sẽ cho kết quả giống nhau.
Serializable: Mức cao nhất. Đảm bảo kết quả thực thi song song các transaction giống hệt như khi chúng chạy tuần tự. Đây là mức "ACID chuẩn nhất" nhưng cũng chậm nhất vì phải khóa (lock) rất nhiều.
Thực tế phũ phàng:
Serializable hiếm khi là mặc định. Hầu hết database (PostgreSQL, MySQL, SQL Server) đều đặt mức mặc định thấp hơn (thường là Read Committed) để đánh đổi lấy hiệu năng.
Thậm chí, Oracle Database ở thời điểm bài viết này còn không hỗ trợ mức Serializable đúng nghĩa.
Bạn phải tự chọn mức phù hợp. Hiểu nghiệp vụ của bạn chịu được đến đâu, và chọn mức isolation tương ứng. Muốn an toàn tuyệt đối? Chọn Serializable. Muốn nhanh? Chọn Read Committed và chấp nhận một số rủi ro nhất quán.
Tóm lại về Isolation: Nó là bức tường ngăn cách các transaction chạy song song. Bạn có thể chọn xây tường thấp (nhanh, dễ nhìn sang) hay tường cao (chậm, an toàn tuyệt đối). Phần lớn thế giới chọn xây tường vừa phải.
Durability (Độ bền vững) - Anh hùng thầm lặng#
Durability nghĩa là: một khi transaction đã commit thành công, thì dù có sập nguồn, hỏng ổ cứng, thì dữ liệu đó cũng phải được đảm bảo không mất.
Đây mới chính là yếu tố quan trọng nhất của ACID, nhưng ít ai để ý và cứ nghĩ nó là điều hiển nhiên. Sự thật là không nhà phát triển nào dám đảm bảo 100% Durability. Tại sao?
Ổ cứng hỏng: Dữ liệu có thể mất vĩnh viễn.
Lệnh
fsyncbị bug: Database dùng lệnh này để đẩy dữ liệu từ RAM xuống ổ cứng. Lệnh này mà có bug hoặc OS không đảm bảo, dữ liệu vẫn có thể "bốc hơi".Replication bất đồng bộ (async): Ghi thành công ở Master, nhưng chưa kịp đẩy sang Slave thì Master "tèo". Thế là mất data gần đây.
Có tồn tại Database nào không có Transaction hay không?#
Như đã nói ở trên, Transaction là một đơn vị công việc nhỏ nhất của Database (không thể nhỏ hơn được nữa). Về bản chất, mọi thao tác đọc/ghi cơ bản (PUT, GET) trong một Key-Value Store đều đã là một transaction rồi.
Transaction có thể gồm 1 operation, hoặc có thể chứa nhiều operation bên trong:
Khi ghi vào Database, thường sẽ cần 2 thao tác: ghi data vào mem và ghi log vào file WAL.
Đối với những Database đang hỗ trợ Secondary Index: Một lệnh
UPDATEđơn giản có thể kích hoạt cập nhật nhiều chỉ mục (index).Nhiều lệnh được Database xử lý rất phức tạp ẩn bên trong nhưng có thể anh em chưa biết, có thể kể đến như:
UPDATE users SET revenue=revenue+10Lệnh
APPENDtrong HBase.Thậm chí một query
SELECT COUNT(*)lớn cũng là một transaction phức tạp.
Như vậy, mình xin khẳng định rằng là:
Tuy nhiên, chúng ta thường quy ước:
"Có transaction" = Hỗ trợ ACID (ít nhất là ở một mức độ nào đó, trên nhiều hàng/object).
"Không có transaction" = Không hỗ trợ hoặc hỗ trợ rất hạn chế ACID.
Gọi thế cho nó lành, cả cộng đồng cùng gọi như vậy rồi mà mình lại không tuân theo thì lại trở thành thằng thượng đẳng mất… cho nên, ngoài đời mình cũng hùa theo như vậy. Nhưng anh em nên hiểu bản chất bên dưới để không bị lừa bởi những tuyên bố kiểu như này:
"Database X có transaction!"
– Đúng là có transaction thật, chỉ là không có ACID mà thôi.
Tại sao nhiều NoSQL không "mặn mà" với ACID?#
Sản phẩm tồn tại theo nhu cầu. "Người dùng" của database chính là lập trình viên. Phần lớn lập trình viên chọn NoSQL vì hai từ: "nhanh" và "dễ scale".
Việc implement ACID, đặc biệt trong môi trường phân tán (distributed systems), là cực kỳ phức tạp và giảm hiệu năng đáng kể. Các hãng NoSQL sẵn sàng hy sinh một nhóm nhỏ người dùng cần ACID, để giữ chân đám đông trung thành với triết lý "tốc độ là số một" của họ.
Tuy nhiên, xu hướng gần đây cho thấy ranh giới đang mờ dần. Nhiều database NoSQL (MongoDB, Cassandra...) đã bổ sung hỗ trợ ACID cho transaction đa-document, trong khi các database quan hệ truyền thống cũng học theo mô hình phân tán của NoSQL. Cuộc chiến đang dần trở thành sự hội tụ.
Tóm lại, cần nhớ gì?#
Transaction là đơn vị công việc cơ bản, không đồng nghĩa với ACID.
ACID là một bộ tiêu chuẩn với 4 thành phần, mỗi thành phần có trade-off riêng:
A (Atomicity): "All or Nothing" - giúp bạn retry an toàn.
C (Consistency): Đảm bảo ràng buộc, nhưng trách nhiệm thuộc về cả DB và lập trình viên.
I (Isolation): Ngăn các transaction song song can thiệp xấu vào nhau.
D (Durability): Đảm bảo dữ liệu đã commit là vĩnh viễn (trong khả năng có thể).
"Hỗ trợ ACID" là một tuyên bố cần được kiểm chứng. Luôn tìm hiểu họ hỗ trợ đến mức độ nào.
Đừng tin marketing mù quáng. Hãy đọc tài liệu, hiểu kiến trúc, và tự benchmark cho use-case của mình.
Sự lựa chọn giữa ACID và hiệu năng là có thật. Hiểu rõ ứng dụng của bạn cần gì sẽ giúp chọn đúng công cụ.
Hy vọng bài viết đầy đủ này giúp anh em nhìn "transaction" và "ACID" bằng con mắt đa chiều và kỹ thuật hơn, từ đó đưa ra những quyết định sáng suốt, tránh xa các "chiếc bánh vẽ" ngọt ngào nhưng rỗng tuếch.