GIAO DỊCH VÀ BỐN TÍNH CHẤT ACID
Vì sao hai quầy thu ngân cùng bán được một hộp quà cuối cùng và giao dịch ngăn điều đó ra sao
Gần mười giờ đêm 30 Tết, kho trung tâm của Sen Vàng Mart, một chuỗi siêu thị giả định, còn đúng một hộp quà Tết loại đặc biệt. Một khách ở cửa hàng Cầu Giấy và một khách ở cửa hàng Đống Đa cùng hỏi mua, cách nhau chưa đầy một giây. Hai máy tính tiền cùng hỏi kho, cùng nhận câu trả lời "còn 1", cùng bán và cùng ghi tồn kho mới là 0. Sáng mùng Hai, kho phát hiện hai phiếu bán cho một hộp quà và một khách phải nhận lời xin lỗi.
Không có dòng mã nào sai trong câu chuyện này. Mỗi máy tính tiền đều làm đúng việc của nó: đọc tồn kho, thấy còn hàng, bán, trừ một. Lỗi nằm ở chỗ hai việc đúng chạy chồng lên nhau. Chuyển dữ liệu từ bảng tính sang cơ sở dữ liệu không tự làm lỗi này biến mất; hệ quản trị cơ sở dữ liệu chỉ ngăn được nó khi ứng dụng dùng đúng công cụ mà hệ quản trị cung cấp. Công cụ đó là giao dịch (transaction), với bốn tính chất quen thuộc được viết tắt là ACID.
Bài viết đi theo trình tự sau. Mục 1 định nghĩa giao dịch và bốn tính chất ACID. Mục 2 cho thấy tính nguyên tử hoạt động ra sao và cạm bẫy của chế độ tự động xác nhận. Mục 3 và mục 4 bàn về tính cô lập: các hiện tượng bất thường khi nhiều giao dịch chạy đồng thời, rồi mức cô lập, khóa và bế tắc. Mục 5 nói về tính bền vững và cái giá của nó. Mục 6 tóm lại thành một danh sách kiểm tra cho người viết ứng dụng. Các ví dụ chạy trên SQLite; số liệu trong bài là kết quả chạy thật.
1. Giao dịch và bốn tính chất ACID
Mục này định nghĩa giao dịch và giải thích từng tính chất bằng tình huống bán hàng.
Giao dịch là một nhóm thao tác mà hệ quản trị coi là một đơn vị công việc logic: hoặc mọi thao tác đều có hiệu lực, hoặc không thao tác nào có hiệu lực (Jukić, 2025). Bán một món hàng ít nhất gồm hai việc: ghi phiếu bán và trừ tồn kho. Hai việc này không có nghĩa khi tách rời; phiếu bán mà không trừ kho thì hàng "biến mất" khỏi sổ sách, trừ kho mà không có phiếu thì hàng "bốc hơi". Giao dịch là cách nói với hệ quản trị rằng chúng đi cùng nhau. Trong SQL, giao dịch bắt đầu bằng BEGIN và kết thúc bằng COMMIT (xác nhận) hoặc ROLLBACK (hoàn tác).
Bốn tính chất mà một giao dịch đúng nghĩa phải có được Haerder và Reuter (1983) đặt tên là ACID. Bảng 1 trình bày từng tính chất qua hậu quả khi thiếu nó.
Bảng 1. Bốn tính chất ACID và hậu quả khi thiếu từng tính chất
| Tính chất | Nghĩa là | Nếu thiếu, ở chuỗi siêu thị sẽ |
|---|---|---|
| Nguyên tử (atomicity) | Tất cả hoặc không gì cả | Mất điện giữa hai lệnh: có phiếu bán mà kho không bị trừ |
| Nhất quán (consistency) | Đi từ một trạng thái hợp lệ tới một trạng thái hợp lệ | Tồn kho âm, vi phạm ràng buộc đã khai báo |
| Cô lập (isolation) | Các giao dịch chạy đồng thời không thấy trạng thái dở dang của nhau | Hai quầy cùng bán hộp quà cuối cùng |
| Bền vững (durability) | Đã báo thành công thì không mất | Máy sập ngay sau khi in hóa đơn: hóa đơn biến mất khỏi sổ |
Nguồn: dựa trên Haerder và Reuter (1983), Jukić (2025).
Bốn tính chất trong Bảng 1 không do cùng một bộ phận lo. Tính nguyên tử và bền vững là việc của bộ quản lý nhật ký trong hệ quản trị. Tính cô lập là việc của bộ điều khiển đồng thời. Riêng tính nhất quán là trách nhiệm chung: hệ quản trị chỉ kiểm được những quy tắc đã được khai báo thành ràng buộc như CHECK, khóa ngoại, UNIQUE; mọi quy tắc nghiệp vụ không được khai báo thì phải do ứng dụng giữ. Đó là lý do bước thiết kế ràng buộc, trình bày trong bài Quy trình phát triển hệ thống cơ sở dữ liệu, quan trọng không kém việc dùng giao dịch.
2. Tính nguyên tử và cạm bẫy tự động xác nhận
Tính nguyên tử nghe hiển nhiên, nhưng có hai chi tiết khiến nhiều ứng dụng vẫn ghi ra dữ liệu mâu thuẫn. Mục này dựng một kho thu nhỏ để thấy cả hai.
Kho có bảng TONKHO với ràng buộc CHECK (SoLuong >= 0) và bảng PHIEUBAN. Ban đầu kho có một hộp quà Tết (mã QT01). Giao dịch dưới đây thử bán hai hộp:
BEGIN;
INSERT INTO PHIEUBAN (MaCH, MaSP, SoLuong) VALUES ('CH02', 'QT01', 2);
UPDATE TONKHO SET SoLuong = SoLuong - 2 WHERE MaSP = 'QT01';
-- Lỗi: CHECK constraint failed: SoLuong >= 0
ROLLBACK;
Lệnh INSERT thành công; lệnh UPDATE vi phạm ràng buộc và bị từ chối. Chi tiết thứ nhất nằm ở đây: khi một câu lệnh trong giao dịch lỗi, SQLite hủy câu lệnh đó nhưng chưa hủy cả giao dịch. Ngay sau lỗi, nếu đọc trong cùng giao dịch, ta vẫn thấy một phiếu bán đang chờ. Ứng dụng phải nhận ra lỗi và chủ động gọi ROLLBACK; khi đó phiếu bán biến mất và dữ liệu trở về trạng thái ban đầu. PostgreSQL xử lý chặt hơn: sau một lỗi, mọi câu lệnh tiếp theo trong giao dịch đều bị từ chối cho đến khi ứng dụng gọi ROLLBACK. Hành vi khác nhau, nhưng trách nhiệm gọi ROLLBACK luôn thuộc về ứng dụng.
Chi tiết thứ hai là chế độ tự động xác nhận (autocommit). Khi không có BEGIN, SQLite và hầu hết hệ quản trị coi mỗi câu lệnh là một giao dịch riêng. Bảng 2 so sánh ba cách chạy cùng hai câu lệnh trên.
Bảng 2. Cùng hai câu lệnh bán hai hộp quà, ba cách chạy, ba kết quả
| Cách chạy | Tồn kho hộp quà sau cùng | Số phiếu bán sau cùng | Dữ liệu |
|---|---|---|---|
| Trong BEGIN, gặp lỗi rồi gọi ROLLBACK | 1 | 0 | Nhất quán |
| Trong BEGIN, gặp lỗi nhưng quên ROLLBACK rồi gọi COMMIT | 1 | 1 | Mâu thuẫn |
| Không có BEGIN (tự động xác nhận) | 1 | 1 | Mâu thuẫn |
Nguồn: kết quả chạy trên SQLite.
Dòng thứ ba của Bảng 2 là lỗi gặp nhiều nhất trong thực tế. Trên màn hình, hai câu lệnh trông như một khối; với hệ quản trị, chúng là hai giao dịch độc lập. Câu INSERT tự xác nhận ngay, câu UPDATE lỗi và không còn gì để hoàn tác. Kết quả là một phiếu bán cho món hàng chưa từng rời kho. Quy tắc rút ra: mọi nhóm lệnh phải thành công cùng nhau thì phải nằm giữa BEGIN và COMMIT và nhánh xử lý lỗi của ứng dụng phải gọi ROLLBACK.
Giao dịch còn là một tấm lưới an toàn cho người làm dữ liệu. Trước một lệnh UPDATE hay DELETE hàng loạt, có thể chạy nó trong BEGIN, đếm số dòng bị ảnh hưởng, xem thử kết quả, rồi mới quyết định COMMIT hay ROLLBACK. Bài SQL từ cơ bản đến nâng cao dùng đúng cách này với một lệnh tăng giá.
3. Tính cô lập và các hiện tượng bất thường
Câu chuyện hộp quà ở đầu bài không phải lỗi nguyên tử; mỗi quầy đều làm trọn việc của mình. Đó là lỗi cô lập. Mục này mô tả các hiện tượng bất thường khi nhiều giao dịch chạy đồng thời và đo tác hại của hiện tượng phổ biến nhất.
Một hệ quản trị phục vụ nhiều người dùng cùng lúc, nên các thao tác của nhiều giao dịch xen kẽ nhau theo thời gian. Phần lớn cách xen kẽ là vô hại. Một số cách xen kẽ cho ra kết quả mà không thứ tự chạy lần lượt nào cho ra được; đó là các hiện tượng bất thường (anomalies). Hình 1 vẽ lại câu chuyện hộp quà dưới dạng một dòng thời gian.

Hình 1. Mất cập nhật: cả hai quầy đọc "còn 1" trước khi quầy kia kịp ghi, nên lần ghi sau đè lên lần ghi trước.
Hiện tượng trong Hình 1 gọi là mất cập nhật (lost update). Ba hiện tượng kinh điển khác được Berenson và cộng sự (1995) phân tích cùng với nó. Đọc dữ liệu chưa xác nhận (dirty read) xảy ra khi một báo cáo đọc tổng tiền của một hóa đơn đang được ghi dở, rồi hóa đơn đó bị hoàn tác; báo cáo chứa một hóa đơn chưa từng tồn tại. Đọc không lặp lại (non-repeatable read) xảy ra khi hai lần đọc giá của cùng một sản phẩm trong một giao dịch cho hai giá khác nhau, vì giữa hai lần có người sửa giá. Bóng ma (phantom) xảy ra khi báo cáo đếm số hóa đơn trong ngày hai lần ra hai con số khác nhau, vì giữa hai lần có hóa đơn mới được chèn thêm.
Để đo tác hại của mất cập nhật, có thể mô phỏng hai quầy bằng hai kết nối thật tới cùng một cơ sở dữ liệu SQLite, chạy xen kẽ theo một lịch cố định để lần chạy nào cũng cho cùng kết quả. Kho có 200 chai nước, mỗi quầy phục vụ 120 lượt khách, mỗi lượt mua một chai. Ba cách viết lệnh bán được thử. Cách 1 là kiểu quen thuộc: đọc tồn kho vào ứng dụng, trừ một, ghi con số mới xuống. Cách 2 dùng một câu UPDATE có điều kiện, để hệ quản trị kiểm tra và trừ trong cùng một câu:
UPDATE TONKHO SET SoLuong = SoLuong - 1
WHERE MaSP = 'SV0101' AND SoLuong >= 1;
-- ứng dụng kiểm tra số dòng bị tác động: 1 là bán được, 0 là hết hàng
Cách 3 giữ kiểu đọc, tính, ghi nhưng đặt trong một giao dịch xin quyền ghi ngay từ đầu (BEGIN IMMEDIATE trong SQLite), buộc quầy thứ hai phải chờ. Hình 2 so sánh kết quả.

Hình 2. Hai quầy bán xen kẽ 240 lượt từ kho 200 chai, theo ba cách viết lệnh bán (mô phỏng trên SQLite).
Cách 1 trong Hình 2 ghi 240 phiếu bán nhưng kho chỉ bị trừ 120 chai. Hệ thống tin rằng kho còn 80 chai trong khi thực tế đã bán vượt 40 chai không có thật. Cách 2 và cách 3 đều đúng: đúng 200 phiếu, kho về 0, 40 lượt khách cuối bị báo hết hàng. Hai cách đúng khác nhau ở cái giá: với cách 3, quầy B phải chờ 120 lần và trong một hệ thống bận, chờ đợi chính là chậm.
Bài học từ Hình 2 không nằm ở cú pháp mà ở cách nghĩ. Kiểu viết "đọc lên ứng dụng, tính, ghi con số tuyệt đối xuống" là kiểu tự nhiên nhất với người lập trình và là nguồn gốc của hầu hết các vụ mất cập nhật, vì giữa lúc đọc và lúc ghi có một khoảng hở. Câu SET SoLuong = SoLuong - 1 thì khác: nó yêu cầu hệ quản trị trừ trên giá trị hiện tại tại đúng thời điểm ghi. Với mọi phép cộng trừ trên số dư, như tồn kho, điểm thưởng, số dư ví, nên để hệ quản trị tính trong một câu lệnh có kèm điều kiện kiểm tra.
4. Mức cô lập, khóa và bế tắc
Ngăn mọi hiện tượng bất thường thì an toàn nhưng chậm, như cách 3 ở Hình 2 đã cho thấy. Mục này giải thích cách các hệ quản trị đánh đổi giữa an toàn và tốc độ.
Hệ quản trị ngăn hiện tượng bất thường bằng hai cơ chế chính. Cơ chế cổ điển là khóa (lock): muốn đọc phải giữ khóa đọc, nhiều giao dịch có thể cùng giữ; muốn ghi phải giữ khóa ghi, chỉ một giao dịch được giữ và chặn mọi khóa khác. Cơ chế thứ hai, phổ biến trong PostgreSQL, MySQL và Oracle, là kiểm soát đồng thời đa phiên bản (multiversion concurrency control, MVCC): mỗi lần ghi tạo một phiên bản mới của dòng, người đọc thấy một "ảnh chụp" nhất quán mà không phải chờ người ghi. Chuẩn SQL định nghĩa bốn mức cô lập (isolation levels) để người dùng chọn mức an toàn cần thiết, như Bảng 3.
Bảng 3. Bốn mức cô lập và các hiện tượng mỗi mức ngăn được
| Mức cô lập | Đọc chưa xác nhận | Đọc không lặp lại | Bóng ma |
|---|---|---|---|
| READ UNCOMMITTED | Có thể xảy ra | Có thể xảy ra | Có thể xảy ra |
| READ COMMITTED | Ngăn | Có thể xảy ra | Có thể xảy ra |
| REPEATABLE READ | Ngăn | Ngăn | Có thể xảy ra |
| SERIALIZABLE | Ngăn | Ngăn | Ngăn |
Nguồn: dựa trên Berenson và cộng sự (1995).
Bảng 3 không có cột mất cập nhật, vì khả năng ngăn hiện tượng này ở các mức giữa phụ thuộc vào cách cài đặt của từng hệ quản trị chứ không chỉ vào tên mức. Berenson và cộng sự (1995) chỉ ra chính các định nghĩa của chuẩn SQL đã mơ hồ ở điểm này. Điều chắc chắn là mức mặc định của các hệ quản trị phổ biến khác nhau, như Bảng 4.
Bảng 4. Mức cô lập mặc định của một số hệ quản trị cơ sở dữ liệu
| Hệ quản trị | Mức mặc định | Ghi chú |
|---|---|---|
| SQLite | SERIALIZABLE | Mỗi thời điểm chỉ một giao dịch được ghi; người ghi khác phải chờ |
| PostgreSQL | READ COMMITTED | Dùng MVCC; kiểu đọc, tính, ghi vẫn có thể mất cập nhật |
| MySQL (InnoDB) | REPEATABLE READ | Dùng MVCC kết hợp khóa |
| SQL Server | READ COMMITTED | Có tùy chọn đọc theo ảnh chụp dựa trên phiên bản |
| Oracle | READ COMMITTED | Dùng MVCC |
Nguồn: dựa trên SQLite (n.d.-a), PostgreSQL Global Development Group (n.d.) và tài liệu của từng hệ quản trị.
Bảng 4 giải thích một lỗi rất khó tái hiện: ứng dụng viết đọc, tính, ghi trong BEGIN và COMMIT chạy đúng trên SQLite khi phát triển, nhưng khi chuyển sang PostgreSQL với mức mặc định READ COMMITTED thì hai quầy vẫn có thể cùng đọc "còn 1". Người phát triển kiểm thử với một người dùng, nơi đồng thời không bao giờ xảy ra. Cách phòng đơn giản nhất là ưu tiên những cách viết đúng ở mọi mức cô lập, như câu UPDATE có điều kiện ở mục 3; khi buộc phải đọc rồi mới quyết định, dùng SELECT ... FOR UPDATE để khóa dòng cần đọc (trên PostgreSQL, MySQL, Oracle) hoặc nâng mức cô lập cho riêng giao dịch đó.
Khóa sinh ra một vấn đề riêng là bế tắc (deadlock). Giả sử giao dịch T1 điều chuyển hàng từ cửa hàng CH01 sang CH02, còn giao dịch T2 điều chuyển theo chiều ngược lại, cùng lúc. T1 khóa dòng tồn kho của CH01 rồi xin khóa CH02; T2 đã khóa CH02 và đang xin khóa CH01. Hai bên chờ nhau mãi mãi. Hệ quản trị phát hiện tình huống này bằng cách tìm vòng tròn trong đồ thị "ai chờ ai", rồi hủy một giao dịch để giao dịch kia đi tiếp; ứng dụng nhận lỗi và cần thử lại. Ở phía ứng dụng, cách giảm bế tắc là luôn khóa theo cùng một thứ tự, chẳng hạn luôn cập nhật cửa hàng có mã nhỏ hơn trước và giữ giao dịch càng ngắn càng tốt.
5. Tính bền vững và cái giá của nó
Tính chất cuối cùng ít được chú ý nhất vì khi nó hoạt động, không ai thấy gì xảy ra. Mục này giải thích cơ chế và đo cái giá của nó.
Tính bền vững được bảo đảm bằng nhật ký giao dịch (transaction log). Trước khi báo COMMIT thành công, hệ quản trị ghi thay đổi vào nhật ký và buộc hệ điều hành đẩy nhật ký xuống đĩa. Nếu máy sập, khi khởi động lại, hệ quản trị đọc nhật ký để làm lại các giao dịch đã xác nhận và bỏ các giao dịch dở dang. SQLite có hai cách tổ chức nhật ký: chế độ mặc định ghi một tệp nhật ký hoàn tác rồi xóa đi sau mỗi giao dịch; chế độ ghi nhật ký trước (write-ahead logging, WAL) ghi thay đổi nối tiếp vào một tệp riêng, vừa ít thao tác đĩa hơn vừa cho phép người đọc và người ghi làm việc đồng thời (SQLite, n.d.-b).
Việc đẩy nhật ký xuống đĩa ở mỗi lần COMMIT tốn thời gian và cái giá đó lộ rõ khi nạp nhiều dòng. Bảng 5 đo thời gian ghi 2.000 hóa đơn vào SQLite theo hai cách: mỗi hóa đơn một giao dịch và cả 2.000 hóa đơn trong một giao dịch.
Bảng 5. Thời gian ghi 2.000 dòng vào SQLite theo cách gom giao dịch
| Chế độ nhật ký | Mỗi dòng một giao dịch | Cả 2.000 dòng một giao dịch | Chênh lệch |
|---|---|---|---|
| Mặc định (DELETE) | 0,88 giây (0,44 ms mỗi dòng) | 0,005 giây | Khoảng 180 lần |
| Ghi nhật ký trước (WAL) | 0,10 giây (0,05 ms mỗi dòng) | 0,004 giây | Khoảng 25 lần |
Nguồn: đo trên SQLite, máy tính xách tay Apple M1 Pro có ổ SSD, chế độ đồng bộ FULL. Số tuyệt đối thay đổi nhiều theo ổ đĩa và hệ điều hành; tỷ lệ thì tương tự.
Bảng 5 cho một bài học thực hành: khi nạp dữ liệu hàng loạt, chẳng hạn chuyển dữ liệu cũ từ bảng tính sang hoặc chạy quy trình nạp kho dữ liệu hằng đêm, hãy gom nhiều dòng vào một giao dịch. Nhưng gom quá lớn cũng có giá: giao dịch càng dài thì giữ khóa càng lâu, làm các giao dịch khác phải chờ và khi lỗi thì phải làm lại nhiều hơn. Cách làm phổ biến là chia thành từng lô vài nghìn dòng, mỗi lô một giao dịch, kèm cơ chế ghi lại lô nào đã xong để chạy tiếp khi có sự cố. Bài ETL là gì và cách nạp dữ liệu vào kho bàn kỹ hơn về việc nạp sao cho chạy lại nhiều lần vẫn không sinh dữ liệu trùng.
Cũng cần nói rõ giới hạn của tính bền vững. Nó bảo vệ trước sự cố như mất điện hay hệ điều hành treo, không bảo vệ trước việc hỏng hẳn ổ đĩa, xóa nhầm hay mã độc tống tiền. Những rủi ro đó thuộc về sao lưu và phục hồi, một công việc khác của người quản trị cơ sở dữ liệu.
6. Danh sách kiểm tra cho người viết ứng dụng
Các mục trên có thể gói lại thành một danh sách ngắn mà người viết ứng dụng hay người duyệt mã có thể dùng ngay. Bảng 6 trình bày danh sách đó, mỗi dòng gắn với tính chất ACID mà nó bảo vệ.
Bảng 6. Danh sách kiểm tra khi viết thao tác ghi dữ liệu
| Câu hỏi kiểm tra | Tính chất | Nếu câu trả lời là "không" |
|---|---|---|
| Các lệnh phải thành công cùng nhau đã nằm giữa BEGIN và COMMIT chưa? | Nguyên tử | Có thể sinh dữ liệu dở dang khi một lệnh lỗi hoặc mất điện |
| Nhánh xử lý lỗi có gọi ROLLBACK không? | Nguyên tử | Phần đã ghi trước lỗi có thể bị xác nhận nhầm |
| Quy tắc nghiệp vụ quan trọng đã thành ràng buộc CHECK, khóa ngoại, UNIQUE chưa? | Nhất quán | Hệ quản trị không chặn được dữ liệu sai |
| Phép cộng trừ trên số dư có được viết thành một câu UPDATE có điều kiện không? | Cô lập | Mất cập nhật khi nhiều người cùng ghi |
| Đã biết mức cô lập mặc định của hệ quản trị đang dùng chưa? | Cô lập | Mã đúng trên máy phát triển có thể sai trên máy thật |
| Giao dịch có chứa thao tác chờ người dùng hoặc gọi dịch vụ bên ngoài không? | Cô lập | Giữ khóa lâu, tăng chờ đợi và bế tắc |
| Nạp hàng loạt đã được chia lô, mỗi lô một giao dịch chưa? | Bền vững | Chậm hàng chục đến hàng trăm lần, hoặc giữ khóa quá lâu |
Dòng thứ sáu của Bảng 6 hay bị bỏ qua nhất. Một giao dịch mở ra rồi chờ khách nhập mã giảm giá, hay chờ cổng thanh toán phản hồi, có thể giữ khóa hàng chục giây. Nguyên tắc chung là giao dịch nên chỉ chứa thao tác trên cơ sở dữ liệu, chuẩn bị mọi thứ khác trước khi BEGIN và làm mọi việc chậm sau khi COMMIT.
Kết luận
Giao dịch là lời hứa mà hệ quản trị cơ sở dữ liệu dành cho ứng dụng: một nhóm thao tác sẽ hoặc xảy ra trọn vẹn, hoặc không xảy ra; dữ liệu đi từ trạng thái hợp lệ này sang trạng thái hợp lệ khác; các giao dịch chạy cùng lúc không giẫm lên nhau; và những gì đã xác nhận sẽ không mất. Nhưng lời hứa đó có điều kiện. Hệ quản trị chỉ biết những lệnh nào thuộc về nhau khi được nói bằng BEGIN và COMMIT, chỉ giữ những quy tắc đã được khai báo và chỉ cô lập đến mức mà nó được cấu hình.
Mô phỏng hai quầy thu ngân cho thấy khoảng cách giữa "mỗi bước đều đúng" và "kết quả đúng" có thể lớn đến mức nào: 240 phiếu bán từ một kho 200 chai, mà hệ thống vẫn tin rằng còn 80. Khoảng cách đó không được lấp bằng phần cứng mạnh hơn hay hệ quản trị đắt hơn, mà bằng những thói quen viết lệnh nhỏ: gom lệnh vào giao dịch, để hệ quản trị tính trên giá trị hiện tại, biết mức cô lập mình đang dùng và giữ giao dịch thật ngắn.
Đọc thêm
- Quy trình phát triển hệ thống cơ sở dữ liệu
- SQL từ cơ bản đến nâng cao
- ETL là gì và cách nạp dữ liệu vào kho
- Kho dữ liệu là gì và khi nào doanh nghiệp cần nó
- Đo chất lượng dữ liệu theo sáu chiều
Tài liệu tham khảo
Berenson, H., Bernstein, P., Gray, J., Melton, J., O'Neil, E., & O'Neil, P. (1995). A critique of ANSI SQL isolation levels. ACM SIGMOD Record, 24(2), 1–10. https://doi.org/10.1145/568271.223785
Haerder, T., & Reuter, A. (1983). Principles of transaction-oriented database recovery. ACM Computing Surveys, 15(4), 287–317. https://doi.org/10.1145/289.291
Jukić, N. (2025). Database systems: Introduction to databases and data warehouses (3rd ed.). Prospect Press. https://www.prospectpressvt.com/textbooks/jukic-database-systems-introduction-to-databases-and-data-warehouses-3-0
PostgreSQL Global Development Group. (n.d.). Transaction isolation. PostgreSQL documentation. https://www.postgresql.org/docs/current/transaction-iso.html
SQLite. (n.d.-a). Isolation in SQLite. https://www.sqlite.org/isolation.html
SQLite. (n.d.-b). Write-ahead logging. https://www.sqlite.org/wal.html