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

Tôi suýt khoe '200 nghìn giao dịch/giây' — rồi nhận ra con số đó nói dối

pgbench cho tôi 203189 tps read-only và tôi mừng rơn. Nhưng con số đó gần như vô nghĩa cho production: không ghi, đĩa VM fsync gần như miễn phí, 150MB nằm gọn trong RAM. Đổi bất kỳ điều kiện nào về phía thật là tps sụp mười mươi. Cách đo tải đúng với pgbench, và vì sao một con số thiếu điều kiện là một con số nói dối.

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

'CTE luôn chậm hơn subquery' — lời khuyên đúng năm 2018, sai 720 lần trên PostgreSQL đời mới

Mang niềm tin cũ 'CTE là hàng rào tối ưu, luôn chậm hơn subquery', tôi viết CTE thường rồi ngồi đợi nó chậm. Đo ra nhanh y hệt subquery — 0,134ms, index scan — vì từ PG12 CTE được inline. Chỉ khi ép AS MATERIALIZED thì hàng rào cũ mới trở lại: 95ms, chậm 720 lần cho cùng logic. Vì sao một luật hiệu năng hết hạn theo phiên bản.

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

Trang 1 nhanh 0,02ms, trang cuối treo 64ms: vì sao OFFSET giết phân trang ở trang sâu

Phân trang OFFSET 0 quét 20 hàng (0,02ms), nhưng OFFSET 500000 quét 500020 hàng (34ms) và trang cuối quét cả triệu hàng (64ms). Chi phí tăng tuyến tính theo độ sâu — và tôi suýt bỏ qua vì chỉ thử trang đầu. Keyset pagination nhảy thẳng qua index, nhanh đều 0,03ms mọi trang. Vì sao OFFSET đếm còn keyset tra cứu.

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

101 truy vấn, câu nào cũng dưới 0,2ms — vì sao đo cục bộ giấu mất thảm họa N+1 qua mạng

Lấy 100 bài rồi mỗi bài một truy vấn bình luận: 101 truy vấn, mỗi câu <0,2ms, đo cục bộ chỉ chậm 3 lần một JOIN nên tôi suýt xem thường. Nhưng qua mạng, 100 round-trip tuần tự làm N+1 chậm 10-100 lần. Số round-trip mới là đại lượng quyết định, và pg_stat_statements với calls=100 là dấu vết để nhận ra.

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

'Đổi subquery thành JOIN cho nhanh' — tôi làm theo và truy vấn chậm đi 10 lần

Tin lời khuyên kinh điển 'subquery chậm, viết lại thành JOIN'. Đo ngược: IN và EXISTS ra kế hoạch y hệt (semi join), và semi join đó nhanh gấp 10 lần JOIN+DISTINCT của tôi — vì DISTINCT nối cả triệu cặp rồi mới khử trùng. Nhãn 'subquery hay join' gần như không quyết định tốc độ; cái quyết định là việc truy vấn thật sự cần làm, đọc được trong EXPLAIN.