
PostgreSQL MVCC là gì: Cơ chế đồng thời hóa trong CSDL
PostgreSQL MVCC (Multi-Version Concurrency Control) là cơ chế cho phép nhiều giao dịch cùng đọc và ghi một bảng mà không phải khóa chặn lẫn nhau. Nhờ đó, PostgreSQL giữ được mức thông lượng cao khi có hàng trăm kết nối đồng thời, nhưng đổi lại mỗi bản ghi bị cập nhật sẽ để lại một phiên bản cũ cho tới khi được dọn dẹp.
MVCC giải quyết vấn đề gì
Ở mô hình khóa truyền thống, một giao dịch ghi sẽ khóa bản ghi và mọi giao dịch khác phải chờ. Khi nhiều phiên cùng ghi vào cùng một bảng hệ thống sẽ nghẽn cổ chai. MVCC đi theo hướng ngược lại: thay vì khóa, hệ thống tạo ra nhiều phiên bản của cùng một dòng dữ liệu và để mỗi giao dịch nhìn thấy một phiên bản khác nhau tùy theo thời điểm bắt đầu.
Trong PostgreSQL, mỗi phiên bản (tuple version) lưu hai số quan trọng:
xmin: ID của giao dịch tạo ra phiên bản này.xmax: ID của giao dịch đã xóa hoặc cập nhật phiên bản, bằng 0 nếu bản ghi còn hiệu lực.
Một giao dịch chỉ thấy dòng dữ liệu khi xmin nhỏ hơn hoặc bằng ID của nó và xmax bằng 0 hoặc lớn hơn ID của nó. Nhờ quy tắc này, mỗi giao dịch luôn thấy một ảnh chụp nhất quán của cơ sở dữ liệu mà không cần khóa.

Các mức cô lập giao dịch liên quan đến MVCC
MVCC là nền tảng để PostgreSQL hiện thực hóa bốn mức cô lập chuẩn của SQL. Trong thực tế, ứng dụng dùng READ COMMITTED cho phần lớn truy vấn vì nó cân bằng giữa tính nhất quán và hiệu năng.
| Mức cô lập | Hiện tượng có thể xảy ra | Khi nào nên dùng |
|---|---|---|
| READ COMMITTED | Đọc cùng một hàng hai lần trong một giao dịch có thể thấy hai giá trị khác nhau | Mặc định cho web, API, báo cáo |
| REPEATABLE READ | Ảnh chụp cố định trong suốt giao dịch, có thể gặp lỗi serialization | Báo cáo tài chính, xử lý đối soát |
| SERIALIZABLE | Chặt chẽ nhất, phát hiện xung đột bằng SSI và phải thử lại giao dịch | Chuyển tiền, chống ghi quá số dư |
| READ UNCOMMITTED | Trong PostgreSQL bị chuyển thành READ COMMITTED | Không nên dùng |
Hệ quả: bản ghi chết và chỉ số phình to
Mỗi lần UPDATE tạo ra một tuple mới thay vì ghi đè. Consequence trực tiếp là cơ sở dữ liệu phình ra và hiệu năng truy vấn giảm dần, đặc biệt khi bảng chỉ nằm trong bộ nhớ đệm mà không đủ cache. Các phiên bản cũ đã bị đánh dấu xóa hoặc không còn nhìn thấy được gọi là dead tuple.
Tiến trình autovacuum của PostgreSQL tự động dọn dead tuple và cập nhật chỉ số. Tuy nhiên trên hệ thống tải nặng, autovacuum có thể không kịp. Kiểm tra bằng câu lệnh:

SELECT relname,
n_live_tup,
n_dead_tup,
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_pct
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;
Nếu tỷ lệ dead vượt 20% thì nên cân nhắc giảm autovacuum_vacuum_scale_factor để vacuum chạy thường xuyên hơn, hoặc lên lịch VACUUM thủ công vào giờ thấp điểm.

MVCC tương tác với chỉ số như thế nào
Mọi loại chỉ số trong PostgreSQL đều hoạt động trên nền MVCC, chỉ khác ở cách xử lý phiên bản cũ:
- B-tree: giữ các mục trỏ tới từng phiên bản tuple, xóa mục khi vacuum dọn. Đây là lý do index bloat thường đi kèm bảng phình.
- Hash: tương tự B-tree nhưng chỉ hỗ trợ so khớp bằng, không hỗ trợ phạm vi.
- GIN: hỗ trợ toán tử mảng, JSONB, tsvector, phù hợp tìm kiếm toàn văn và truy vấn theo khóa.
- GiST: dùng cây tìm kiếm tổng quát cho hộp giới, không gian, chuỗi ký tự.
- BRIN: rất nhỏ vì chỉ lưu thống kê theo khối trang, hợp dữ liệu ghi tuần tự như log, timestamp.
Khi bảng bị REINDEX thường xuyên vì bloat, cần kiểm tra lại tỷ lệ dead tuple trước đó. REINDEX CONCURRENTLY không khóa bảng ghi nhưng chậm hơn và tốn nhiều I/O hơn.
Cấu hình và vận hành thực tế
Ba tham số nên để ý khi vận hành cơ sở dữ liệu dùng nhiều MVCC:
default_transaction_isolation: đặt mặc định ứng dụng nếu muốn thay đổi hành vi toàn cục.autovacuum_vacuum_cost_delay: hạ xuống khi bảng bị cập nhật liên tục để vacuum theo kịp.autovacuum_max_workers: tăng trên server có nhiều bảng nóng, giá trị mặc định 3 thường không đủ.
Ngoài ra, hãy giữ giao dịch ngắn gọn. Giao dịch kéo dài giữ snapshot lâu, chặn vacuum dọn các phiên bản cũ và làm bảng phình nhanh hơn. Với tác vụ nhập dữ liệu lớn, cách an toàn là chia thành nhiều lô nhỏ, mỗi lô một giao dịch.
Chi tiết chính thức về MVCC và các mức cô lập nằm trong tài liệu chính thức của PostgreSQL, phần MVCC và Isolation. Bạn cũng có thể xem bảng tham số autovacuum tại cấu hình autovacuum.
Hiểu MVCC giúp bạn tránh phần lớn lỗi hiệu năng âm thầm trên PostgreSQL: bảng phình to, truy vấn chậm dần theo thời gian, hay khoá chết giữa hai ứng dụng cùng ghi vào cùng một bảng.
