Cơ sở dữ liệu 22/09/2026 5 phút

Bảng PostgreSQL ngày càng chậm: phân biệt bloat với thiếu index, chữa đúng bệnh

Một bảng chậm dần theo thời gian có hai nguyên nhân rất khác nhau: bloat (phình do dead tuple) hay thiếu index. Bài này đo thật một bảng phình từ 21 lên 176 MB do UPDATE, cách phân biệt bằng dead tuple và kế hoạch, rồi chữa bằng VACUUM đúng loại. PostgreSQL 16.

Lập trình 22/09/2026 6 phút

MVCC và VACUUM: vì sao UPDATE làm bảng phình 9 lần dù số dòng không đổi

PostgreSQL không sửa dòng tại chỗ — mỗi UPDATE tạo một phiên bản mới và để lại 'xác chết' (dead tuple). Bài này đo thật: một bảng 100.000 dòng sau 8 lần UPDATE toàn bộ phình từ 3.5MB lên 31MB với 799.810 dead tuple, dù vẫn đúng 100.000 dòng sống. VACUUM dọn dead tuple (dead về 0) nhưng không trả đĩa; chỉ VACUUM FULL trả bảng về 3.5MB — nhưng khoá cả đọc. Hiểu MVCC để không bị bloat bất ngờ.

Lập trình 22/09/2026 6 phút

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.

Lập trình 22/09/2026 6 phút

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.

Lập trình 22/09/2026 9 phút

VACUUM, bloat và wraparound đo thật: mười lần UPDATE toàn bảng biến 27 MB thành 296 MB, VACUUM giải phóng 90% chỗ nhưng không trả đĩa, autovacuum mặc định không chạy lần nào trong 60 giây tải, và một giao dịch mở giữ 600.000 dead tuple không dọn được

Đo trên PostgreSQL 16 với pgstattuple: bảng 200.000 dòng sau 10 UPDATE toàn bảng phình từ 27 MB lên 296 MB, VACUUM 599 ms đưa dead về 0,13% và free lên 90% nhưng kích thước giữ nguyên, VACUUM FULL 278 ms trả về 27 MB. Dưới pgbench 60 giây trên 3 triệu dòng, autovacuum mặc định chạy 0 lần và dead tuple lên 96.000; chỉnh scale 0.01, naptime 5 giây, cost_delay 0 thì chạy 4 lần. Một giao dịch mở 40 giây làm VACUUM báo 600.000 dead but not yet removable và bảng phình 108 lên 189 MB dù vacuum đều. Hạ freeze_max_age xuống 100.000 rồi đốt 160.000 XID: 773 lần aggressive vacuum giữ age dưới ngưỡng.