PostgreSQL nội bộ và đồng thời: MVCC, isolation, khóa đo thật

Sê-ri nâng cao về cơ chế bên trong PostgreSQL cho lập trình viên backend: MVCC, các mức cô lập giao dịch, khóa, deadlock, vacuum, WAL — mỗi bài demo THẬT bằng nhiều phiên psql đồng thời trong Docker, quan sát số liệu/hành vi thật.

12/12 phần đã đăng Lập trình
1 MVCC trong PostgreSQL: vì sao đọc không chặn ghi, và UPDATE không sửa tại chỗ Nhiều database khóa hàng khi đọc để tránh đọc dở dang — PostgreSQL thì không, nhờ MVCC. Bài này đo thật với hai phiên psql đồng thời: khi phiên A UPDATE balance=999 chưa commit, phiên B vẫn đọc được balance=100 (bản cũ) mà không bị chặn. Xem metadata ẩn xmin/xmax/ctid để hiểu vì sao UPDATE tạo phiên bản mới thay vì sửa tại chỗ. 22/09/2026 · 6 phút đọc 2 Bốn mức cô lập giao dịch PostgreSQL: đọc bẩn, đọc lặp sai, phantom — đo thật từng cái Mức cô lập giao dịch quyết định một giao dịch được thấy gì từ giao dịch khác đang chạy. Bài này đo thật trên pg-lab từng bất thường: ở Read Committed đọc cùng hàng hai lần ra 100 rồi 200 (đọc lặp sai), ở Repeatable Read vẫn 100 (an toàn). Và sự thật ít người biết: PostgreSQL không có đọc bẩn kể cả khi đặt Read Uncommitted. 22/09/2026 · 6 phút đọc 3 Read Committed vs Repeatable Read: cùng code, kết quả khác — và lost update âm thầm Hai mức cô lập trông giống nhau cho đến khi có ghi đồng thời. Bài này đo thật trên pg-lab mẫu đọc-tính-ghi kinh điển: ở Read Committed, hai giao dịch cùng +10 cho kết quả 110 thay vì 120 — lost update âm thầm, không báo lỗi. Ở Repeatable Read, PostgreSQL phát hiện và báo could not serialize access, buộc thử lại — và lần thử lại cho đúng 120. 22/09/2026 · 6 phút đọc 4 Serializable và write skew: lỗi đồng thời mà Repeatable Read không bắt được Có một loại bug đồng thời tinh vi hơn lost update: hai giao dịch đọc chung một điều kiện rồi ghi hai hàng khác nhau, phá vỡ một ràng buộc mà không cái nào tự biết. Bài này đo thật trên pg-lab ví dụ hai bác sĩ cùng xin nghỉ: ở Repeatable Read cả hai thành công và còn 0 người trực (sai), ở Serializable một giao dịch bị hủy với lỗi read/write dependencies và ràng buộc được giữ. 22/09/2026 · 7 phút đọc 5 Khóa hàng trong PostgreSQL: FOR UPDATE, lock wait, và cách chẩn đoán query bị treo MVCC giúp đọc không chặn ghi, nhưng khi cần 'đọc rồi chắc chắn ghi' bạn phải khóa hàng tường minh bằng FOR UPDATE. Bài này đo thật trên pg-lab: phiên thứ hai chờ 2.535 ms cho phiên thứ nhất nhả khóa cùng hàng, quan sát pg_locks và wait_event=Lock/transactionid, và NOWAIT báo lỗi ngay thay vì chờ. Hai view vàng để chẩn đoán treo vì khóa. 22/09/2026 · 6 phút đọc 6 Deadlock trong PostgreSQL: khóa chéo, 'deadlock detected', và cách tránh bằng thứ tự khóa Hai giao dịch khóa chéo nhau — A giữ hàng 1 xin hàng 2, B giữ hàng 2 xin hàng 1 — là công thức của deadlock. Bài này tạo deadlock thật trên pg-lab: PostgreSQL tự phát hiện vòng chờ và hủy một nạn nhân với ERROR deadlock detected, giao dịch kia đi tiếp. Và cách tránh triệt để: luôn khóa các hàng theo cùng một thứ tự. 22/09/2026 · 6 phút đọc 7 Bloat trong PostgreSQL: vì sao cập nhật một hàng 100.000 lần làm bảng phình lên 3,5 MB MVCC khiến mỗi UPDATE để lại một bản chết thay vì sửa tại chỗ — và chúng tích tụ thành bloat. Bài này đo thật trên pg-lab: một bảng chỉ có 1 hàng sống, sau 100.000 lần UPDATE phình từ 8 KB lên 3,5 MB. VACUUM dọn bản chết nhưng không trả đĩa; chỉ VACUUM FULL mới thu nhỏ file về 8 KB — nhưng khóa bảng độc quyền. 22/09/2026 · 6 phút đọc 8 VACUUM và autovacuum: cơ chế dọn bản chết tự động giữ PostgreSQL sống sót Bloat (bài trước) sẽ bóp nghẹt database nếu không được dọn — và VACUUM là công cụ dọn đó. Bài này đo thật trên pg-lab: autovacuum TỰ chạy khi dead tuple vượt ngưỡng, đưa n_dead_tup từ 10.000 về 0 mà không cần một lệnh nào. Xem VACUUM VERBOSE báo cáo dọn 5000 tuple, không gian được tái dùng giữ kích thước ổn định, và vì sao đừng bao giờ tắt autovacuum. 22/09/2026 · 6 phút đọc 9 HOT update và index-only scan: hai tối ưu MVCC mà fillfactor và index che phủ mở khóa UPDATE trong PostgreSQL thường phải ghi lại mọi index — trừ khi là HOT update. Bài này đo thật trên pg-lab: với fillfactor mặc định 100, cập nhật cột không-index vẫn cho 0 HOT (trang đầy); hạ xuống 70 thì 4.368/10.000 là HOT (không ghi index). Và index-only scan cho Heap Fetches=0 khi query chỉ cần cột trong index, giảm I/O rõ rệt. 22/09/2026 · 6 phút đọc 10 WAL trong PostgreSQL: vì sao ghi log trước khi ghi dữ liệu, và đo lượng WAL mỗi thao tác sinh ra Độ bền của PostgreSQL đến từ WAL (write-ahead log): mọi thay đổi ghi vào log tuần tự và fsync xuống đĩa trước, trang dữ liệu ghi sau. Bài này đo thật trên pg-lab lượng WAL sinh ra: INSERT 10.000 hàng tạo 861 kB, COPY cùng dữ liệu chỉ 357 kB, UPDATE nặng nhất 1.645 kB, còn SELECT không sinh WAL. Kèm đánh đổi fsync và synchronous_commit. 22/09/2026 · 6 phút đọc 11 SKIP LOCKED: xây hàng đợi công việc ngay trong PostgreSQL, 4 worker không đụng nhau Bạn không phải lúc nào cũng cần RabbitMQ hay SQS cho hàng đợi công việc — PostgreSQL làm được bằng SELECT FOR UPDATE SKIP LOCKED. Bài này đo thật trên pg-lab: 4 worker chạy song song vét sạch 300 job, chia đều 76/75/75/74, mỗi job xử lý đúng một lần, không trùng, không chờ khóa. Kèm advisory lock cho khóa mức ứng dụng. 22/09/2026 · 6 phút đọc 12 Tổng kết đồng thời PostgreSQL: khung tư duy và checklist tránh bug concurrency Mười một bài, mười một phép đo thật bằng nhiều phiên psql đồng thời. Bài tổng kết này nối tất cả — MVCC, bốn mức cô lập, lost update, write skew, khóa hàng, deadlock, bloat, vacuum, HOT, WAL, SKIP LOCKED — thành một bảng số liệu và một checklist thực chiến: cần đảm bảo gì thì dùng gì, cạm bẫy đồng thời nào cần tránh, và vì sao mọi thứ bắt nguồn từ MVCC. 22/09/2026 · 6 phút đọc