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

Isolation level: tái hiện thật non-repeatable read và write skew, và vì sao READ COMMITTED không đủ

Transaction chạy đồng thời sinh ra các hiện tượng bất thường mà một mình bạn khó hình dung. Bài này tái hiện THẬT trong PostgreSQL bằng hai session: ở READ COMMITTED, cùng một transaction đọc balance hai lần ra 1000 rồi 500 (non-repeatable read); REPEATABLE READ giữ snapshot ổn định nên đọc lại vẫn 1000; và write skew — hai bác sĩ cùng xin nghỉ trực khiến còn 0 người trực ở REPEATABLE READ, nhưng SERIALIZABLE bắt được và abort một transaction.

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

Window function: tính theo nhóm mà vẫn giữ từng dòng — và nhanh hơn self-join 124 lần

GROUP BY gộp nhóm lại làm mất chi tiết từng dòng. Window function tính toán theo nhóm (xếp hạng, tổng luỹ kế, so kỳ trước) mà VẪN giữ mọi dòng — thứ mà nhiều người viết bằng self-join phức tạp và chậm. Bài này đo thật trong PostgreSQL: ROW_NUMBER/RANK/DENSE_RANK khác nhau khi có tie, running total + LAG, và một benchmark đắt giá — window làm running total trên 30.000 dòng mất 15.9ms, còn self-join tương quan chỉ 3.000 dòng đã mất 1971ms.

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

CTE và recursive CTE: viết query dễ đọc, duyệt cây — và bẫy MATERIALIZED chậm 1000 lần

CTE (WITH) chia query rối rắm thành các bước có tên, dễ đọc; recursive CTE duyệt cây/đồ thị chỉ bằng một câu SQL. Nhưng có một bẫy hiệu năng: bài này đo thật trong PostgreSQL — cùng một query lấy WHERE id=500000 trên bảng 1 triệu dòng, CTE thường (inline) dùng Index Scan chỉ 0.098ms, còn thêm MATERIALIZED buộc quét cả 1 triệu dòng mất 99.756ms — chậm hơn 1000 lần. Kèm recursive CTE dựng cây tổ chức từ CEO xuống.

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

Phân trang: vì sao OFFSET 1 triệu chậm 3000 lần, và keyset pagination giữ tốc độ phẳng

OFFSET LIMIT là cách phân trang ai cũng viết, và nó hoạt động hoàn hảo... cho tới trang thứ 50.000. Bài này đo thật trên bảng 2 triệu dòng: OFFSET chậm dần tuyến tính — trang đầu 0.029ms nhưng trang cuối 144.941ms, vì nó phải đọc qua 1.000.020 dòng chỉ để lấy 20. Keyset pagination (WHERE id > last_id) giữ ~0.04ms phẳng ở mọi trang. Kèm plan chứng minh OFFSET quét rồi vứt, keyset nhảy thẳng.

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

Statistics và planner: vì sao query 'đột nhiên chậm' — khi database đoán sai số dòng

Planner PostgreSQL không biết dữ liệu của bạn — nó ƯỚC LƯỢNG từ thống kê rồi chọn plan. Thống kê sai thì chọn plan tệ. Bài này đo thật: một bảng chèn 500.000 dòng nhưng chưa ANALYZE khiến planner ước lượng 1 dòng trong khi thực tế 5000 (chọn plan chậm 15.5ms); ANALYZE đưa về đúng, plan nhanh gấp đôi. Và cột tương quan (city suy ra country) khiến planner ước lượng thấp 4 lần — CREATE STATISTICS sửa từ ~23.000 về 99.500 sát thực tế 100.000.