Cơ sở dữ liệu 03/09/2026 8 phút

Đánh index lên cột boolean rồi đợi truy vấn nhanh — nhưng PostgreSQL phớt lờ nó, và nó đúng

Truy vấn chọn lọc (1% hàng) dùng index nhanh 4,7 lần; nhưng trên cột boolean 50% hàng, planner bỏ index chọn seq scan. Tôi tưởng 'có index thì phải dùng index'. Index chỉ giúp khi truy vấn trả về ít hàng — và một index chết còn âm thầm làm chậm mọi lần ghi.

Cơ sở dữ liệu 03/09/2026 8 phút

Index (a,b) tăng tốc WHERE a nhưng bỏ rơi WHERE b — vì sao thứ tự cột quyết định tất cả

Index (a,b) tăng tốc WHERE a=? và WHERE a=? AND b=? (4 trang) — nhưng WHERE b=? một mình cho Seq Scan quét cả 5406 trang. Tôi tưởng (a,b) là hai index cho a và b; hóa ra nó là một cây sắp theo a trước. Quy tắc tiền tố trái nhất, và vì sao thứ tự cột là quyết định thiết kế.

Cơ sở dữ liệu 03/09/2026 8 phút

Bảng 81MB nhỏ hơn cache 128MB mà chạy 3 lần vẫn không nóng lên — bí mật của ring buffer

Lần đọc đầu là shared read (nạp trang), lần sau thành shared hit (từ RAM). Nhưng một seq scan bảng lớn KHÔNG vào cache dù bảng nhỏ hơn shared_buffers — vì ring buffer cố ý bảo vệ cache khỏi bị một lần quét lớn xóa sạch. Tôi tưởng 'chạy lại luôn nhanh hơn', hóa ra đó không phải luật tự nhiên.

Cơ sở dữ liệu 03/09/2026 8 phút

Hàng 8 byte tưởng nhét 1024 dòng một trang — đo ra chỉ 226, phần còn lại đi đâu?

Một hàng 8 byte dữ liệu (2 int) tưởng 1024 hàng/trang 8KB, đo thật chỉ 226 — vì mỗi tuple còn cõng 23 byte header MVCC + con trỏ + đệm = 36 byte. Tôi nhẩm phép chia mà quên chi phí cố định mỗi hàng. Mổ trang thật bằng pageinspect: gần 78% mỗi hàng hẹp là overhead, không phải dữ liệu.

Cơ sở dữ liệu 03/09/2026 8 phút

Một hàng UPDATE 10.000 lần phình bảng lên 360KB — vì PostgreSQL không hề sửa tại chỗ

UPDATE một cột không sửa tại chỗ: ctid đổi từ (0,1) sang (0,2), bản cũ thành dead tuple. Tôi tưởng UPDATE ghi đè giá trị, bảng giữ nguyên; đo ra một hàng UPDATE 10.000 lần làm bảng phình từ 8KB lên 360KB với 10.000 xác chết. Đây là MVCC — mỗi thay đổi để lại một phiên bản cũ, và vì sao đọc-ghi đồng thời mượt.