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

Merge join: 77ms khi dữ liệu đã sắp, 160ms khi phải tự sắp — và vì sao đừng vội chê nó thua hash

Merge join trên khóa đã sắp: 77ms không cần sort; nhưng trên cột chưa sắp phải thêm Sort tràn đĩa, 160ms (hash chỉ 78ms). Tôi suýt kết luận 'merge dở hơn hash' — cho tới khi thấy mình đo nó ở đúng điều kiện bất lợi. Đổi lại nó giữ nguyên thứ tự: ORDER BY LIMIT nhanh 0,03ms so với 72ms của hash.

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

ORDER BY ... LIMIT 10 không sắp cả triệu hàng như bạn tưởng — nó chỉ giữ 10, tốn 25kB

ORDER BY trên 1 triệu hàng: work_mem nhỏ thì external merge tràn đĩa 208ms, lớn thì quicksort RAM 162ms. Nhưng ORDER BY LIMIT 10 dùng top-N heapsort chỉ 25kB, 53ms — không sắp hết. Đừng đoán thuật toán từ câu SQL; EXPLAIN cho biết cơ sở dữ liệu thật sự sắp thế nào, và một index có thể bỏ luôn bước sắp.

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

Cùng 2 triệu hàng, GROUP BY này tốn 24kB còn kia tràn 62MB ra đĩa — khác nhau ở đâu?

GROUP BY 100 nhóm trên 2 triệu hàng dùng HashAggregate chỉ 24kB; nhưng GROUP BY 2 triệu nhóm phân biệt làm nó tràn 62MB ra đĩa. Tôi suýt kết luận 'GROUP BY nhẹ RAM bất kể bảng lớn'. Bộ nhớ tính theo SỐ NHÓM phân biệt, không phải số hàng — và đó cũng là cái bẫy của COUNT(DISTINCT).

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

Planner đoán WHERE v<10 khớp 333350 hàng, thực tế 10000 — không phải nó dở, nó đang bị bịt mắt

Bảng vừa nạp 1 triệu hàng chưa ANALYZE: planner ước lượng WHERE v<10 là 333350 hàng, thực tế 10000 — lệch 33 lần. Tôi suýt trách planner ngu. Chạy ANALYZE, ước lượng về 9872, khớp. Thống kê bảng là nguồn của mọi ước lượng, và một planner mù thống kê buộc phải đoán bằng hằng số mặc định.

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 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.