Tối ưu hóa database với PostgreSQL
Sê-ri thực chiến, đầy đủ về tối ưu hiệu năng PostgreSQL: từ đọc EXPLAIN, index, kế hoạch truy vấn, cách viết SQL, thiết kế schema, MVCC và VACUUM, cấu hình bộ nhớ, khóa và đồng thời, phân vùng, sao chép tới giám sát — mỗi kỹ thuật đo bằng số liệu thật.
132/132 phần đã đăng
Cơ sở dữ liệu
1
Tối ưu PostgreSQL bắt đầu từ đâu? Không phải từ mẹo hay ho, mà từ một câu lệnh: EXPLAIN ANALYZE
Bài mở đầu sê-ri tối ưu PostgreSQL. Vì sao 'đo trước, đoán sau' là nguyên tắc số một, và cách EXPLAIN (ANALYZE, BUFFERS) cho bạn con số thật thay vì niềm tin — minh hoạ trên bảng 1 triệu dòng, đo trước và sau.
22/09/2026
· 5 phút đọc
2
Đo trên 10 dòng dữ liệu là tự lừa mình: dựng môi trường benchmark PostgreSQL đàng hoàng với pgbench
Trước khi tối ưu phải có mốc đo tin cậy. Cách dùng pgbench sinh dữ liệu lớn và đo TPS/latency thật, phân biệt tải đọc và đọc-ghi, và những nguyên tắc để con số không bị nhiễu bởi cache lạnh hay dữ liệu tí hon.
22/09/2026
· 4 phút đọc
3
Đọc EXPLAIN không phải đọc từ trên xuống: cách lần một kế hoạch truy vấn PostgreSQL từ trong ra ngoài
EXPLAIN in ra một cái cây, không phải danh sách. Hiểu cấu trúc node, ba con số cost-rows-width, và thứ tự chạy thật để biết PostgreSQL định làm gì với truy vấn của bạn — qua một kế hoạch Hash Join thật.
22/09/2026
· 4 phút đọc
4
Planner đoán 29.651 dòng, thực tế 99.344: EXPLAIN ANALYZE và nghệ thuật bắt PostgreSQL đoán sai
EXPLAIN ANALYZE chạy thật rồi in số dòng thực tế bên cạnh ước lượng. Khoảng cách giữa hai con số đó là manh mối số một của truy vấn chậm. Xem một ước lượng lệch 3,3 lần vì cột tương quan, và cách sửa.
22/09/2026
· 4 phút đọc
5
read=1740 hay hit=1740? Thêm BUFFERS vào EXPLAIN để biết truy vấn đọc từ RAM hay từ đĩa
Cùng một kế hoạch có thể nhanh hay chậm tuỳ dữ liệu nằm trong cache hay trên đĩa. Tuỳ chọn BUFFERS của EXPLAIN cho bạn thấy số trang shared hit so với read, chìa khoá hiểu cache lạnh và nóng — đo thật trên PostgreSQL 16.
22/09/2026
· 4 phút đọc
6
Con số cost=133520.03 từ đâu ra? Giải mã cost model của PostgreSQL bằng một phép nhân khớp đến từng số lẻ
Planner quy mọi kế hoạch về một con số cost để so sánh. Hiểu công thức từ seq_page_cost, cpu_tuple_cost, và tự tay tính lại đúng cost một Seq Scan — rồi thấy planner luôn chọn phương án cost thấp nhất.
22/09/2026
· 4 phút đọc
7
Truy vấn chậm 81ms không phải kẻ thù: pg_stat_statements và bài học 'nhanh nhưng nhiều' đánh bại 'chậm nhưng ít'
Trong hàng nghìn truy vấn của hệ thống thật, tối ưu cái nào trước? pg_stat_statements xếp hạng theo tổng thời gian, và số liệu thật cho thấy một truy vấn 0.007ms có thể ngốn nhiều hơn truy vấn 81ms gấp 7 lần.
22/09/2026
· 5 phút đọc
8
Đặt một dòng cấu hình, PostgreSQL tự tố cáo mọi truy vấn chậm: log_min_duration_statement trong thực chiến
pg_stat_statements cho bức tranh tổng thể, nhưng để bắt tận tay một câu chậm cá biệt kèm nguyên văn tham số, bạn cần log_min_duration_statement. Xem một truy vấn 98ms tự động vào log, câu nhanh thì không.
22/09/2026
· 4 phút đọc
9
Truy vấn chậm lúc 3 giờ sáng, bạn ngủ: auto_explain ghi sẵn kế hoạch ANALYZE vào log để sáng ra mổ
log_min_duration_statement cho biết câu nào chậm, nhưng không cho biết vì sao. auto_explain tự đính EXPLAIN ANALYZE đầy đủ vào log cho truy vấn chậm, kèm số dòng và buffer thật, không cần chạy lại.
22/09/2026
· 4 phút đọc
10
Hết căng mắt đọc cây thụt lề: EXPLAIN (FORMAT JSON) và những công cụ biến kế hoạch thành sơ đồ màu
EXPLAIN mặc định in text khó đọc với truy vấn phức tạp. FORMAT JSON cho ra dữ liệu có trường tên, dán vào công cụ trực quan để có cây màu tô đỏ node chậm. Xem JSON thật và cách dùng.
22/09/2026
· 4 phút đọc
11
13.713 trang index, nhưng tìm 1 dòng trong 5 triệu chỉ đọc 4 trang: bên trong B-tree của PostgreSQL
B-tree là loại index mặc định và quan trọng nhất. Hiểu cấu trúc cây cân bằng ba tầng của nó — soi bằng pageinspect — giải thích vì sao tìm kiếm nhanh cỡ logarit, nền tảng cho mọi quyết định đánh index.
22/09/2026
· 4 phút đọc
12
Vì sao PostgreSQL 'không thèm' dùng index của bạn? Đo thật ngưỡng độ chọn lọc index giúp và index hại
Index không phải lúc nào cũng nhanh hơn. Khi truy vấn lấy nhiều dòng, quét tuần tự lại rẻ hơn — và planner cố tình bỏ index. Đo thời gian thật cả hai chiều để thấy planner chọn đúng, và hiểu độ chọn lọc.
22/09/2026
· 4 phút đọc
13
Cùng một index nhiều cột, đổi cột nào đứng đầu WHERE: 0,033 ms hay 109 ms — quy tắc cột trái nhất
Index nhiều cột không phải cứ liệt kê các cột là dùng được cho mọi truy vấn. Thứ tự cột quyết định. Hiểu quy tắc cột trái nhất qua danh bạ điện thoại, và đo thật chênh lệch 3.300 lần.
22/09/2026
· 4 phút đọc
14
Heap Fetches: 0 — khi PostgreSQL trả kết quả mà không thèm mở bảng: index-only scan và INCLUDE
Index thường tìm xong vẫn phải đọc heap để lấy các cột khác. Covering index dùng INCLUDE nhét sẵn cột vào index, cho index-only scan bỏ hẳn bước đọc bảng. Đo thật cost giảm 95 lần, kèm điều kiện VACUUM.
22/09/2026
· 5 phút đọc
15
97% dữ liệu không bao giờ tra tới, sao lại index nó? Partial index nhỏ hơn 13 lần cho đúng phần bạn cần
Nếu bạn chỉ hay truy vấn một phần dữ liệu (đơn chờ xử lý, tài khoản active), index cả bảng là lãng phí. Partial index chỉ đánh những dòng thỏa điều kiện, nhỏ hơn nhiều và ghi nhẹ hơn. Đo thật 34 MB vs 2,6 MB.
22/09/2026
· 4 phút đọc
16
WHERE lower(email) = ... chậm 320ms dù đã có index trên email: vì sao, và expression index cứu thế nào
Bọc một hàm quanh cột trong WHERE là làm mất index thường — PostgreSQL phải tính hàm cho từng dòng. Expression index đánh index trên chính biểu thức đó. Đo thật 320ms xuống 0,018ms.
22/09/2026
· 4 phút đọc
17
Ràng buộc duy nhất trong PostgreSQL: một index B-tree đứng sau, và cái bẫy NULL ai cũng vấp
UNIQUE constraint và CREATE UNIQUE INDEX khác nhau ở đâu, vì sao ràng buộc duy nhất vừa cưỡng chế vừa tăng tốc đọc, và tại sao một cột UNIQUE vẫn cho nhiều dòng NULL — đo thật trên PostgreSQL 16 với lỗi duplicate key nguyên văn.
22/09/2026
· 6 phút đọc
18
Bitmap scan trong PostgreSQL: khi planner gộp hai index cho một truy vấn
BitmapAnd và BitmapOr cho phép PostgreSQL dùng nhiều index cùng lúc trên một bảng. Xem cách nó biến hai index kém chọn lọc thành một truy vấn nhanh, vì sao phải qua bitmap thay vì index scan thẳng, và đọc Recheck Cond — đo thật trên PostgreSQL 16.
22/09/2026
· 6 phút đọc
19
GIN index trong PostgreSQL: đánh chỉ mục bên trong jsonb, mảng và văn bản
B-tree đánh chỉ mục cả giá trị; GIN đánh chỉ mục từng thành phần bên trong nó — mỗi khoá jsonb, mỗi phần tử mảng, mỗi từ trong văn bản. Đo thật ba truy vấn trên bảng 1 triệu dòng: jsonb nhanh gấp 27 lần, full-text gấp 18 lần, và cái giá phải trả.
22/09/2026
· 6 phút đọc
20
GiST index trong PostgreSQL: tìm điểm gần nhất và khoảng thời gian giao nhau
B-tree cần một trục để sắp; điểm hai chiều và khoảng thời gian thì không có. GiST là cây của các hộp bao, cho phép tìm láng giềng gần nhất (KNN) và overlap khoảng. Đo thật: KNN nhanh gấp 578 lần, range overlap gấp 15 lần trên PostgreSQL 16.
22/09/2026
· 5 phút đọc
21
BRIN index trong PostgreSQL: index 40 kB thay cho B-tree 214 MB trên bảng 10 triệu dòng
BRIN chỉ lưu min/max cho mỗi khối trang thay vì một mục cho mỗi dòng, nên index nhỏ hơn hàng nghìn lần B-tree. Với bảng log/time-series đã sắp theo thứ tự tự nhiên, nó nhanh gần bằng B-tree mà tốn gần như không có dung lượng — đo thật trên PostgreSQL 16.
22/09/2026
· 6 phút đọc
22
Hash index trong PostgreSQL: chỉ làm được phép so bằng, nhưng khi nào thì đáng dùng?
Hash index chỉ trả lời được câu hỏi 'khoá này bằng X?' — không range, không ORDER BY, không LIKE. Đổi lại, với khoá dài nó nhỏ hơn B-tree nhiều lần. Đo thật trên bảng 5 triệu phiên, token 101 ký tự, PostgreSQL 16.
22/09/2026
· 5 phút đọc
23
CREATE INDEX CONCURRENTLY: tạo index trên bảng đang chạy mà không chặn ghi
CREATE INDEX thường lấy ShareLock khóa mọi INSERT/UPDATE/DELETE tới khi build xong — trên bảng production đó là sự cố. CONCURRENTLY dùng lock nhẹ hơn cho ghi chạy song song. Đo thật lock mode, INSERT bị chặn, và cái giá phải trả trên PostgreSQL 16.
22/09/2026
· 5 phút đọc
24
Chi phí ẩn của index: mỗi index bạn thêm làm chậm mọi lần ghi bao nhiêu?
Index tăng tốc đọc nhưng đánh thuế lên mọi INSERT/UPDATE/DELETE. Đo thật: INSERT 500 nghìn dòng chậm gấp 6 lần khi có 7 index, và cơ chế HOT update giúp né chi phí đó khi cột đổi không có index. PostgreSQL 16.
22/09/2026
· 5 phút đọc
25
Index bloat trong PostgreSQL: vì sao index phình 8 lần, và cách đo mức phình thật
Mỗi UPDATE cột có index để lại mục chết; VACUUM dọn chúng nhưng không trả dung lượng về hệ điều hành. Đo thật: index phình từ 21 MB lên 172 MB sau 10 lần update, và vì sao chỉ REINDEX mới co lại được. PostgreSQL 16 với pgstattuple.
22/09/2026
· 5 phút đọc
26
REINDEX và REINDEX CONCURRENTLY: dựng lại index để xóa bloat mà không khóa bảng
REINDEX co index bloat 172 MB về 21 MB, nhưng nó khóa ghi cả bảng. REINDEX CONCURRENTLY dùng lock nhẹ hơn cho ghi chạy song song. Đo thật lock mode, thời gian, và cách dọn index INVALID trên PostgreSQL 16.
22/09/2026
· 4 phút đọc
27
Tìm và xóa index không dùng trong PostgreSQL: idx_scan = 0 và last_idx_scan
Index không truy vấn nào chạm tới chỉ tốn chi phí ghi và dung lượng mà không đổi lại gì. Đo thật cách phát hiện qua pg_stat_user_indexes, cột last_idx_scan mới của PostgreSQL 16, và xóa an toàn — kèm những cạm bẫy khi kết luận vội.
22/09/2026
· 5 phút đọc
28
Nhiều index đơn cột hay một index ghép? Đo thật và luật thứ tự cột
Với truy vấn lọc nhiều cột cùng lúc, một index ghép nhanh gấp 4 lần và nhỏ hơn hai index đơn cột. Nhưng thứ tự cột quyết định tất cả — luật leftmost prefix. Đo thật trên bảng 3 triệu dòng, PostgreSQL 16.
22/09/2026
· 5 phút đọc
29
Seq Scan, Index Scan, Bitmap Heap Scan: ba kiểu quét và vì sao planner chọn từng cái
PostgreSQL chọn kiểu quét theo số dòng truy vấn trả về, không phải theo việc có index hay không. Đo thật ba kiểu trên bảng 3 triệu dòng, và chứng minh vì sao ép dùng index cho truy vấn lấy nửa bảng chậm gấp 9 lần. PostgreSQL 16.
22/09/2026
· 5 phút đọc
30
Nested Loop Join trong PostgreSQL: nhanh 0,044 ms hay chậm 627 ms, tùy một index
Nested Loop là kiểu join đơn giản nhất — với mỗi dòng bảng ngoài, tìm dòng khớp ở bảng trong. Nó là thiên đường khi bảng ngoài nhỏ và bảng trong có index, nhưng thành thảm họa O(n×m) khi thiếu index. Đo thật trên PostgreSQL 16.
22/09/2026
· 5 phút đọc
31
Hash Join trong PostgreSQL: nối hai bảng lớn bằng bảng băm, và cái bẫy tràn đĩa
Khi cả hai bảng đều lớn, Hash Join đọc mỗi bảng đúng một lần và thắng Nested Loop gấp 5 lần. Nhưng nếu bảng băm vượt work_mem, nó tràn ra đĩa và chậm hẳn. Đo thật hai pha build và probe, Batches, và cách canh work_mem trên PostgreSQL 16.
22/09/2026
· 5 phút đọc
32
Merge Join trong PostgreSQL: khi hai luồng đã sắp thắng cả Hash Join
Merge Join nối hai luồng đã sắp bằng cách chạy song song hai con trỏ — không cần bảng băm khổng lồ. Đo thật: trên join 3 triệu × 3 triệu, nó nhanh hơn Hash Join khi dữ liệu đã có index, nhưng chậm lại nếu phải Sort trước. PostgreSQL 16.
22/09/2026
· 5 phút đọc
33
Planner chọn join nào và vì sao: cùng một câu JOIN, ba kế hoạch khác nhau
PostgreSQL không có join mặc định — với mỗi truy vấn nó ước lượng số dòng rồi tính chi phí từng kiểu join và chọn cái rẻ nhất. Đo thật: cùng câu JOIN, đổi bộ lọc WHERE khiến planner chuyển từ Nested Loop sang Hash Join. PostgreSQL 16.
22/09/2026
· 5 phút đọc
34
Sort và work_mem trong PostgreSQL: sắp trong RAM, tràn ra đĩa, hay khỏi sắp luôn
ORDER BY, DISTINCT và Merge Join đều cần sắp dữ liệu, và work_mem quyết định sắp trong RAM hay tràn ra đĩa. Đo thật ba Sort Method, cách top-N heapsort tối ưu LIMIT, và vì sao một index xóa hẳn bước Sort. PostgreSQL 16.
22/09/2026
· 5 phút đọc
35
HashAggregate vs GroupAggregate: hai cách PostgreSQL thực thi GROUP BY
Cùng một câu GROUP BY, PostgreSQL chọn HashAggregate hay GroupAggregate tùy số nhóm. Đo thật: ít nhóm thì bảng băm 48 kB thắng đậm, nhiều nhóm thì bảng băm tràn 193 MB ra đĩa. Và vai trò của đầu vào đã sắp. PostgreSQL 16.
22/09/2026
· 5 phút đọc
36
Thống kê bảng và ANALYZE: nền tảng của mọi quyết định planner, và cái bẫy thống kê cũ
Planner chọn scan, join, aggregate dựa trên ước lượng số dòng — và ước lượng đó lấy từ thống kê trong pg_stats. Đo thật: thống kê cũ khiến planner ước lượng sai 900.000 lần, và ANALYZE sửa ngay. PostgreSQL 16.
22/09/2026
· 5 phút đọc
37
default_statistics_target: chỉnh độ chi tiết thống kê để planner ước lượng đúng hơn
Tham số này quyết định PostgreSQL giữ bao nhiêu giá trị MCV và bucket histogram. Đo thật trên cột phân bố lệch: target thấp làm ước lượng sai 2,3 lần, tăng lên thì gần chính xác — kèm cái giá phải trả. PostgreSQL 16.
22/09/2026
· 5 phút đọc
38
Extended statistics (CREATE STATISTICS): dạy planner biết các cột tương quan nhau
Planner giả định các cột độc lập và nhân xác suất — nên với cột tương quan, nó ước lượng sai vài lần. CREATE STATISTICS khai báo mối liên hệ. Đo thật: ước lượng từ thiếu 3 lần thành khớp, và số nhóm GROUP BY từ 18 về đúng 6. PostgreSQL 16.
22/09/2026
· 5 phút đọc
39
Vì sao planner ước lượng sai: khi PostgreSQL mất thống kê và đoán mù
Planner chỉ dùng được thống kê khi điều kiện có dạng cột-toán tử-hằng số. Bọc cột trong hàm, dùng LIKE tiền tố mở, hay so cột với cột — nó mất thống kê và rơi về đoán mặc định. Đo thật bốn nguyên nhân và cách sửa từng loại. PostgreSQL 16.
22/09/2026
· 5 phút đọc
40
Parallel query trong PostgreSQL: chia một truy vấn cho nhiều worker cùng quét
Với truy vấn quét bảng lớn, PostgreSQL chia việc cho nhiều tiến trình worker chạy song song rồi gom lại. Đo thật: quét 5 triệu dòng nhanh gấp 3,5 lần với 4 worker, và vì sao truy vấn nhỏ không parallel. PostgreSQL 16.
22/09/2026
· 5 phút đọc
41
JIT trong PostgreSQL: khi nào giúp, khi nào hại — và cái bẫy làm query nhanh chậm 800 lần
JIT biên dịch biểu thức truy vấn thành mã máy để chạy nhanh hơn, nhưng biên dịch tốn thời gian trả trước. Đo thật: JIT kích hoạt nhầm biến một query 0,045 ms thành 37,7 ms, và không thắng cả trên aggregate 5 triệu dòng. PostgreSQL 16.
22/09/2026
· 6 phút đọc
42
SELECT * là một phản mẫu: đo thật cái giá lấy thừa cột trong PostgreSQL
SELECT * lấy mọi cột kể cả cột lớn bạn không dùng — phá index-only scan, truyền dữ liệu gấp hàng chục lần, và vỡ khi schema đổi. Đo thật: SELECT * chậm gấp 9,5 lần và truyền 189 MB thay vì 2 MB. PostgreSQL 16.
22/09/2026
· 5 phút đọc
43
Phân trang trong PostgreSQL: OFFSET chậm dần theo trang, keyset giữ tốc độ không đổi
OFFSET buộc PostgreSQL quét rồi vứt bỏ mọi dòng trước trang cần — trang càng sâu càng chậm. Keyset pagination dùng điều kiện thay OFFSET, seek thẳng qua index. Đo thật: trang cuối OFFSET mất 142 ms, keyset chỉ 0,024 ms. PostgreSQL 16.
22/09/2026
· 5 phút đọc
44
N+1 query: vì sao một vòng lặp gọi truy vấn giết hiệu năng, và cách gộp thành một
N+1 là mẫu 1 truy vấn lấy danh sách rồi N truy vấn con trong vòng lặp — thường do ORM sinh ngầm. Đo thật: chi phí không đến từ query (100 query server-side chỉ 0,71 ms) mà từ 100 round-trip (4,3 giây). Một JOIN xóa sạch. PostgreSQL 16.
22/09/2026
· 5 phút đọc
45
EXISTS vs IN vs JOIN trong PostgreSQL: cái nào nhanh hơn, và cạm bẫy NOT IN với NULL
Ba cách viết cùng câu hỏi có tồn tại. Đo thật: EXISTS và IN cho kế hoạch giống hệt (semi join), JOIN+DISTINCT chậm hơn. Và cạm bẫy nguy hiểm: NOT IN với NULL cho kết quả sai lặng lẽ. PostgreSQL 16.
22/09/2026
· 5 phút đọc
46
CTE (WITH) trong PostgreSQL: materialize hay không, và thay đổi lớn ở PG12
Trước PG12, mọi CTE là rào chắn tối ưu — luôn materialize. Từ PG12, CTE đơn giản được inline, đẩy filter xuống dùng index. Đo thật: CTE materialize chậm hơn 4.700 lần vì không đẩy được filter. Và khi nào cần MATERIALIZED chủ ý. PostgreSQL 16.
22/09/2026
· 5 phút đọc
47
Subquery tương quan chạy N lần: khi nào rẻ, khi nào thành 41 giây
Subquery tương quan tham chiếu cột truy vấn ngoài nên chạy lại một lần cho mỗi dòng ngoài (loops=N trong EXPLAIN). Có index thì rẻ, không có thì O(n×m). Đo thật: cùng truy vấn 41 giây với subquery không index vs 90 ms với JOIN. PostgreSQL 16.
22/09/2026
· 5 phút đọc
48
Window function trong PostgreSQL: tính tổng hợp mà vẫn giữ từng dòng, và tối ưu chúng
Window function tính tổng hợp theo nhóm nhưng giữ nguyên mỗi dòng — thay thế self-join và subquery tương quan. Đo thật: bước tốn nhất là Sort theo PARTITION BY, một index khớp xóa hẳn nó, và gom các hàm cùng window để sắp một lần. PostgreSQL 16.
22/09/2026
· 5 phút đọc
49
DISTINCT vs GROUP BY trong PostgreSQL: khi nào giống nhau, và anti-pattern che JOIN xấu
DISTINCT và GROUP BY cho kế hoạch giống hệt khi khử trùng. Nhưng SELECT DISTINCT thường bị lạm dụng để che dòng trùng do JOIN sai — đo thật một truy vấn 26 giây nhân dòng thành 300 triệu. Cùng DISTINCT ON tiện lợi. PostgreSQL 16.
22/09/2026
· 5 phút đọc
50
UNION vs UNION ALL trong PostgreSQL: bước khử trùng ẩn tốn kém mà bạn không thấy
UNION = UNION ALL + một bước khử trùng toàn bộ kết quả. Đo thật: trên hai tập rời nhau, UNION vẫn sắp 4 triệu dòng (tràn đĩa 47 MB) để loại đúng 0 dòng, chậm hơn 2,8 lần vô ích. Và khi nào UNION là lựa chọn đúng. PostgreSQL 16.
22/09/2026
· 6 phút đọc
51
LATERAL join trong PostgreSQL: top-N mỗi nhóm gọn gàng, nhanh hơn window function
LATERAL cho subquery bên phải tham chiếu cột bảng bên trái — điều subquery thường trong FROM không làm được. Đo thật: lấy 3 đơn mới nhất mỗi khách bằng LATERAL + LIMIT + index chạy 158 ms, đọc 600 nghìn trang; window row_number phải quét cả 3 triệu dòng, 540 ms, 3 triệu trang. PostgreSQL 16.
22/09/2026
· 6 phút đọc
52
Điều kiện OR làm mất index trong PostgreSQL: khi nào, vì sao, và cách cứu
OR không tự động giết index — nó chỉ nhanh bằng nhánh chậm nhất. Đo thật: OR trên hai cột có index cho BitmapOr 0,09 ms, nhưng chỉ một nhánh thiếu index kéo cả câu thành Seq Scan toàn bảng 48 ms. Và vì sao index gộp phục vụ AND mà không phục vụ OR. PostgreSQL 16.
22/09/2026
· 6 phút đọc
53
Hàm bọc quanh cột làm mất index trong PostgreSQL: lower(), date() và cách gỡ
Index lưu giá trị cột, không lưu giá trị của hàm. Bọc lower(email) hay date(tao_luc) quanh cột trong WHERE khiến index thành vô dụng. Đo thật: lower(email) chạy Seq Scan 160 ms so với 0,036 ms; date(tao_luc) 47,6 ms so với 0,433 ms khi viết lại thành khoảng. Index biểu thức và điều kiện sargable. PostgreSQL 16.
22/09/2026
· 6 phút đọc
54
Ép kiểu ngầm định làm mất index trong PostgreSQL: hàm ẩn quanh cột
So sánh cột với sai kiểu dữ liệu khiến PostgreSQL chèn ép kiểu — và nếu nó ép vế cột, index chết. Đo thật: int8 so với numeric literal chạy Seq Scan 55 ms so với 0,04 ms; hướng ép quyết định tất cả. Cách đọc EXPLAIN để phát hiện và cách sửa. PostgreSQL 16.
22/09/2026
· 6 phút đọc
55
Tìm kiếm LIKE và ILIKE trong PostgreSQL: index nào cho kiểu mẫu nào
LIKE 'abc%' neo đầu dùng được btree — nhưng với locale khác C phải có opclass text_pattern_ops, nếu không vẫn Seq Scan. LIKE '%abc%' bao giữa thì không btree nào giúp, phải dùng trigram pg_trgm. Đo thật cả bốn ca và ILIKE. PostgreSQL 16.
22/09/2026
· 5 phút đọc
56
Full-text search trong PostgreSQL: tsvector, tsquery và GIN index
Full-text search tìm theo từ đã chuẩn hóa gốc và bỏ stop word — điều LIKE không làm được: query khớp cả queries. Đo thật: FTS không index còn chậm hơn LIKE (2,49 giây), nhưng cột tsvector STORED cộng GIN index đưa về 90 ms. Cùng ts_rank xếp hạng liên quan. PostgreSQL 16.
22/09/2026
· 6 phút đọc
57
Trigram pg_trgm trong PostgreSQL: tìm gần đúng, chịu được lỗi gõ
pg_trgm đo độ tương tự chuỗi bằng tỉ lệ trigram chung, nên tìm được cả khi gõ sai chính tả — điều LIKE và full-text search không làm được. Đo thật: KNN với GiST 190 ms, ngưỡng với GIN 104 ms, và Postgersql gõ sai vẫn tìm ra PostgreSQL. Chọn GiST hay GIN. PostgreSQL 16.
22/09/2026
· 6 phút đọc
58
Chọn kiểu số cho đúng trong PostgreSQL: numeric, float, int và căn lề
float không dùng cho tiền vì sai số làm tròn: 0.1 cộng 0.2 không bằng 0.3. Đo thật: int8 tốn gấp đôi int4, numeric tính chậm hơn 50 phần trăm, và cùng bộ cột chỉ đổi thứ tự đã chênh 77 MB vì căn lề. Cách chọn kiểu số đúng. PostgreSQL 16.
22/09/2026
· 5 phút đọc
59
text vs varchar vs char trong PostgreSQL: chọn kiểu chuỗi nào và vì sao
Trong PostgreSQL, text và varchar lưu và chạy y hệt nhau — varchar(n) chỉ thêm một ràng buộc độ dài. Đo thật: char(50) tốn gấp đôi text vì đệm khoảng trắng, và gây bẫy so sánh khi độ dài logic khác byte trên đĩa. Vì sao char cố định nhanh hơn là huyền thoại. PostgreSQL 16.
22/09/2026
· 5 phút đọc
60
Sắp thứ tự cột để giảm padding trong PostgreSQL: cùng bảng, khác dung lượng
PostgreSQL căn lề mỗi cột theo mốc kiểu của nó, chèn byte đệm khi lệch. Đo thật: cùng 7 cột y hệt, thứ tự xấu chiếm 403 MB còn thứ tự tối ưu chỉ 287 MB — chênh 116 MB chỉ do thứ tự khai báo. Quy tắc sắp xếp và truy vấn tự tìm thứ tự tối ưu. PostgreSQL 16.
22/09/2026
· 5 phút đọc
61
JSONB vs cột riêng trong PostgreSQL: linh hoạt đổi lấy điều gì
JSONB gom mọi trường vào một cột linh hoạt, nhưng đo thật: nó tốn gấp đôi dung lượng vì lặp tên khoá mỗi dòng, truy vấn chậm hơn, và index GIN lớn gấp 11 lần btree cột. Khi nào chọn cột riêng, khi nào JSONB, và mô hình lai. PostgreSQL 16.
22/09/2026
· 5 phút đọc
62
Mảng vs bảng con trong PostgreSQL: gọn nhẹ đổi lấy toàn vẹn
Lưu danh sách bằng cột mảng int[] hay bảng con chuẩn hóa với khóa ngoại? Đo thật: mảng gọn hơn 2 lần, tìm chứa giá trị nhanh hơn với index GIN tí hon, khỏi JOIN. Nhưng mảng không có khóa ngoại nên chấp nhận cả giá trị không tồn tại. Chọn theo nhu cầu toàn vẹn. PostgreSQL 16.
22/09/2026
· 5 phút đọc
63
Khóa chính bigserial vs UUID trong PostgreSQL: cái giá của ngẫu nhiên
UUID v4 ngẫu nhiên phân tán toàn cục nhưng làm chèn chậm và index phình. Đo thật: chèn 2 triệu dòng mất 3,5 giây với UUID so với 1,3 giây bigserial, và index PK to gấp 1,8 lần. Vì sao ngẫu nhiên hại btree, và UUIDv7 sửa điều đó thế nào. PostgreSQL 16.
22/09/2026
· 5 phút đọc
64
Khóa ngoại có cần index không trong PostgreSQL: có, và nó không tự tạo
PostgreSQL tự tạo index cho PRIMARY KEY và UNIQUE nhưng KHÔNG cho cột khóa ngoại. Đo thật: thiếu index đó, xóa một dòng cha phải quét cả 5 triệu dòng con (179 ms so với 0,7 ms), và JOIN cha con seq scan 52 ms so với 0,17 ms. Vì sao, và khi nào vẫn bỏ được. PostgreSQL 16.
22/09/2026
· 5 phút đọc
65
Chuẩn hóa vs phi chuẩn hóa trong PostgreSQL: JOIN có thật sự đắt không
Nhiều người phi chuẩn hóa vì sợ JOIN đắt. Đo thật: JOIN tới bảng nhỏ có index gần như miễn phí (0,192 ms so với 0,197 ms không JOIN), trong khi cập nhật dữ liệu sao chép chậm 175 lần và rủi ro lệch. Khi nào chuẩn hóa, khi nào phi chuẩn có lý. PostgreSQL 16.
22/09/2026
· 6 phút đọc
66
TOAST trong PostgreSQL: lưu giá trị lớn ra sao và vì sao SELECT * đắt
TOAST tự nén giá trị lớn rồi đẩy phần còn lớn ra bảng phụ, giữ bảng chính gọn. Đo thật: bảng chính chỉ 15 MB dù tổng dữ liệu hơn 1 GB, và đọc không chạm cột lớn nhanh hơn 33 lần. Nén xảy ra trước, chiến lược lưu, và vì sao SELECT * tốn kém. PostgreSQL 16.
22/09/2026
· 6 phút đọc
67
Ràng buộc giúp planner trong PostgreSQL: không chỉ bảo vệ dữ liệu
NOT NULL, CHECK, UNIQUE, PRIMARY KEY không chỉ chặn dữ liệu sai — chúng là thông tin planner dùng để bỏ bước thừa. Đo thật: một CHECK cho phép planner bỏ hẳn quét bảng (0,025 ms so với 40 ms), và PRIMARY KEY mở khóa phụ thuộc hàm trong GROUP BY. PostgreSQL 16.
22/09/2026
· 6 phút đọc
68
MVCC trong PostgreSQL: vì sao mỗi UPDATE tạo một dòng mới
PostgreSQL không sửa dòng tại chỗ — mỗi UPDATE viết một phiên bản dòng mới và để lại bản cũ làm dòng chết. Đo thật: ctid đổi sau UPDATE, và 100.000 lần cập nhật một dòng logic làm bảng phình từ 8KB lên 3,5MB. Vì sao MVCC làm vậy, và HOT giảm bloat ra sao. PostgreSQL 16.
22/09/2026
· 6 phút đọc
69
Bloat trong PostgreSQL: bảng phình vì dòng chết, cách phát hiện và dọn
DELETE và UPDATE để lại dòng chết; VACUUM biến chúng thành chỗ trống nhưng file không nhỏ lại — đó là bloat. Đo thật: xóa 95% dòng nhưng file vẫn 269 MB, và bảng bloat đọc chậm 3,7 lần vì quét cả trang trống. Cách phát hiện bằng pgstattuple và dọn từ VACUUM FULL tới pg_repack. PostgreSQL 16.
22/09/2026
· 5 phút đọc
70
VACUUM trong PostgreSQL: dọn dead tuple, và ba việc ít ai biết
VACUUM không chỉ dọn rác. Đo thật qua VERBOSE: nó xóa dead tuple, dọn con trỏ index tới chúng, và cập nhật visibility map — mở khóa index-only scan thật, đưa Heap Fetches từ 300 nghìn về 0. Cùng vai trò freeze chống XID wraparound. PostgreSQL 16.
22/09/2026
· 5 phút đọc
71
Autovacuum trong PostgreSQL: công thức ngưỡng, cạm bẫy bảng lớn, và theo dõi
Autovacuum chạy khi dead tuple vượt threshold cộng scale_factor nhân số dòng sống. Đo thật: scale_factor mặc định 0.2 khiến bảng 100 triệu dòng phải tích 20 triệu dead tuple mới chạy — bloat khổng lồ. Cách chỉnh per-table và theo dõi qua pg_stat_user_tables. PostgreSQL 16.
22/09/2026
· 5 phút đọc
72
VACUUM FULL trong PostgreSQL: khi nào cần và ba rủi ro phải biết
VACUUM FULL thu nhỏ file bảng thật, nhưng đo thật: nó giữ khóa ACCESS EXCLUSIVE chặn cả đọc lẫn ghi (SELECT bị hủy vì lock timeout), cần thêm gấp đôi đĩa tạm, và downtime tỉ lệ kích thước bảng. Khi nào dùng, và vì sao production 24/7 nên dùng pg_repack. PostgreSQL 16.
22/09/2026
· 6 phút đọc
73
HOT update trong PostgreSQL: cập nhật một dòng mà không đụng index
HOT (Heap-Only Tuple) cho UPDATE tạo phiên bản mới ngay trong cùng trang, không cập nhật index nào — nếu không đổi cột được index và trang còn chỗ. Đo thật: update cột không index đạt 100 phần trăm HOT giữ index nguyên, update cột có index 0 phần trăm HOT làm index phình. PostgreSQL 16.
22/09/2026
· 5 phút đọc
74
fillfactor trong PostgreSQL: chừa chỗ trong trang để HOT hoạt động
fillfactor chừa phần trăm chỗ trống trong mỗi trang khi INSERT, để phiên bản UPDATE mới ở lại cùng trang và HOT phát huy. Đo thật: fillfactor 100 cho 0 phần trăm HOT và phình 6 lần, fillfactor 70 cho 72 phần trăm HOT chỉ phình 2,4 lần. Cách chọn giá trị theo tải ghi. PostgreSQL 16.
22/09/2026
· 5 phút đọc
75
Transaction ID wraparound trong PostgreSQL: hiểm họa 32-bit và VACUUM freeze
Transaction id là số 32-bit, cửa sổ dùng được chỉ khoảng 2 tỉ. Nếu dòng cũ không được freeze trước khi bộ đếm tràn, chúng bỗng trông như tương lai và biến mất — thảm họa. Đo thật cách theo dõi age(datfrozenxid) và vì sao VACUUM bắt buộc kể cả bảng chỉ đọc. PostgreSQL 16.
22/09/2026
· 6 phút đọc
76
pgstattuple trong PostgreSQL: đo bloat chính xác, và bản xấp xỉ nhanh
pgstattuple quét cả bảng để cho con số bloat chính xác qua free_percent. Đo thật: bảng 297 MB chỉ 30 phần trăm là dữ liệu sống, 64,8 phần trăm là chỗ trống; bản approx dùng visibility map nhanh hơn 25 lần với sai số nhỏ; và pgstatindex đo bloat index riêng. PostgreSQL 16.
22/09/2026
· 5 phút đọc
77
pg_repack trong PostgreSQL: dọn bloat online mà không khóa bảng
pg_repack thu nhỏ bảng bloat như VACUUM FULL nhưng chạy online — đọc và ghi không bị chặn. Đo thật: repack một bảng 1894 MB xuống 631 MB trong khi một SELECT đồng thời vẫn trả về 2,67 triệu dòng, thay vì bị hủy như với VACUUM FULL. Cơ chế năm bước và lưu ý production. PostgreSQL 16.
22/09/2026
· 5 phút đọc
78
shared_buffers trong PostgreSQL: đặt bao nhiêu, và vì sao không phải càng nhiều càng tốt
shared_buffers là bộ nhớ đệm chính của PostgreSQL, mặc định 128MB thường quá nhỏ. Đo thật: bảng lớn hơn nó chỉ đạt 37 phần trăm cache hit, nhưng tập nóng vẫn ở lại. Quy tắc 25 phần trăm RAM và vì sao đặt lớn hơn lãng phí vì double buffering với OS cache. PostgreSQL 16.
22/09/2026
· 5 phút đọc
79
work_mem trong PostgreSQL: RAM cho sort và hash, và cạm bẫy per-connection
work_mem là RAM tối đa cho mỗi thao tác sort hoặc hash. Đo thật: sort 2 triệu dòng tràn 35 MB ra đĩa với work_mem nhỏ, làm trong RAM khi đủ; hash join chia 16 batch. Nhưng nó là per-operation per-connection nên đặt quá lớn có thể hết bộ nhớ. Cách tính an toàn. PostgreSQL 16.
22/09/2026
· 5 phút đọc
80
maintenance_work_mem trong PostgreSQL: RAM cho VACUUM và CREATE INDEX
maintenance_work_mem là RAM cho VACUUM, CREATE INDEX, REINDEX — tách khỏi work_mem và đặt cao hơn được. Đo thật: đủ RAM giữ danh sách dead tuple thì VACUUM chỉ quét index 1 lần thay vì 58 lần; nhưng tác dụng lên CREATE INDEX lại khiêm tốn. Cách đặt an toàn. PostgreSQL 16.
22/09/2026
· 5 phút đọc
81
effective_cache_size trong PostgreSQL: gợi ý cache đổi hẳn kế hoạch mà không tốn 1 byte RAM
effective_cache_size không cấp phát bộ nhớ — nó chỉ là con số cho planner đoán chi phí Index Scan lặp lại. Đo thật: cùng một truy vấn, đặt 4MB planner quét tuần tự 5 triệu dòng mất 408 ms, đặt 32GB planner chọn Nested Loop + Index Scan 15,8 ms — nhanh gấp 26 lần. Vì sao mặc định 4GB thường quá thấp. PostgreSQL 16.
22/09/2026
· 6 phút đọc
82
random_page_cost cho SSD: vì sao mặc định 4.0 làm PostgreSQL né index oan
random_page_cost là tỉ số nói với planner đọc trang ngẫu nhiên đắt gấp mấy lần đọc tuần tự. Mặc định 4.0 hợp ổ cứng cơ HDD (thời gian seek), nhưng quá cao với SSD. Đo thật: cùng truy vấn khớp 1% dữ liệu, rpc=4.0 quét tuần tự cả bảng mất 84 ms, rpc=1.1 dùng index còn 7 ms — nhanh gấp 12 lần. Và cạm bẫy hạ quá tay. PostgreSQL 16.
22/09/2026
· 6 phút đọc
83
effective_io_concurrency: prefetch cho Bitmap Heap Scan, và vì sao đo trên cache thì thấy nó vô dụng
effective_io_concurrency cho phép Bitmap Heap Scan nạp trước nhiều trang heap song song bằng posix_fadvise. Mặc định 1 là quá thấp cho SSD (nên 200-300). Nhưng đo thật trên container đã cache: eic=1 và eic=200 cho thời gian y hệt — vì prefetch chỉ che được độ trễ đọc đĩa THẬT. Bài học về cách benchmark tham số này cho đúng. PostgreSQL 16.
22/09/2026
· 6 phút đọc
84
WAL và checkpoint trong PostgreSQL: vì sao UPDATE 100k dòng sinh 48 MB WAL
Mọi thay đổi ghi vào WAL trước để đảm bảo độ bền; checkpoint dồn các trang bẩn ra đĩa. Đo thật: cùng một UPDATE 100k dòng sinh 48 MB WAL ngay sau checkpoint (2606 full-page image) nhưng chỉ 28 MB khi lặp lại — chênh 20 MB là chi phí full-page image. Và cách đọc checkpoints_req để biết max_wal_size quá nhỏ. PostgreSQL 16.
22/09/2026
· 6 phút đọc
85
wal_compression và full_page_writes: giảm WAL an toàn, và cái bẫy tắt full_page_writes
full_page_writes tạo ra full-page image trong WAL (chống torn page); wal_compression nén chúng lại. Đo thật: bật wal_compression giảm WAL 17-30% tuỳ độ nén của dữ liệu, FPI vẫn còn nên vẫn an toàn. Tắt full_page_writes giảm 35% nhưng bỏ hẳn chống torn page — gần như không bao giờ nên làm. PostgreSQL 16.
22/09/2026
· 6 phút đọc
86
Huge pages cho PostgreSQL: vì sao shared_buffers 8GB tạo 2 triệu mục page table
Trang nhớ thường 4KB khiến shared_buffers lớn sinh hàng triệu mục page table, làm TLB của CPU quá tải. Huge pages 2MB giảm số mục đi 512 lần. Đo thật trạng thái container: huge_pages=try âm thầm fallback, THP đang bù 2MB. Vì sao nên dùng huge page rõ ràng và tắt THP. PostgreSQL 16.
22/09/2026
· 6 phút đọc
87
Cache hit ratio đọc cho đúng: vì sao 100% vẫn có thể là truy vấn tệ
Cache hit ratio 99% được coi là chỉ số sức khỏe, nhưng nó đánh lừa theo ba cách. Đo thật: một truy vấn self-join có hit ratio 100% (không chạm đĩa) vẫn mất 1,4 giây vì nhân 25 triệu dòng. Ratio đo dữ liệu đến từ đâu, không đo truy vấn tốt hay xấu — công cụ đúng là EXPLAIN BUFFERS. PostgreSQL 16.
22/09/2026
· 6 phút đọc
88
Vì sao nhiều kết nối PostgreSQL tốn RAM — và vì sao con số ps đánh lừa bạn
PostgreSQL fork một tiến trình riêng cho mỗi kết nối, nên nhiều kết nối tốn RAM và CPU. Đo thật: 60 kết nối tạo 60 tiến trình, ps RSS gợi ý 16 MB/kết nối nhưng đó là con số ảo do đếm trùng shared_buffers — RAM thật của kết nối idle chỉ ~0,44 MB. Vì sao vấn đề thật nằm ở kết nối hoạt động và context switch. PostgreSQL 16.
22/09/2026
· 6 phút đọc
89
Chi phí một kết nối PostgreSQL: đo thật vì sao pool nhanh gấp 18 lần
Một kết nối PostgreSQL có hai loại chi phí: thiết lập (fork backend, xác thực, nạp catalog) và bộ nhớ tích lũy. Đo thật: mở lại kết nối mỗi truy vấn cho 1.306 TPS so với 24.228 TPS khi tái dùng — chậm 18 lần. Và bộ nhớ backend phình từ 1,3 MB lên 2,1 MB khi chạm nhiều bảng. Vì sao pooling là giải pháp kỹ thuật. PostgreSQL 16.
22/09/2026
· 6 phút đọc
90
max_connections đặt bao nhiêu: throughput bị chặn bởi số core, không phải RAM
Đặt max_connections cao không cho throughput cao hơn — số truy vấn chạy song song bị chặn bởi số core CPU. Đo thật trên máy 10 core: TPS đạt đỉnh ở ~16 client rồi TỤT 20% khi lên 90 client vì context switch. Vì sao max_connections là trần an toàn chứ không phải mục tiêu, và công thức thực tế. PostgreSQL 16.
22/09/2026
· 5 phút đọc
91
PgBouncer: gom kết nối cho PostgreSQL — đo thật mở lại kết nối nhanh 4,7 lần
PgBouncer là lớp pooler nhẹ gom hàng trăm client vào vài chục kết nối thật tới PostgreSQL. Đo thật với PgBouncer 1.25: mở lại kết nối qua pooler cho 15.824 TPS so với 3.368 TPS trực tiếp — nhanh 4,7 lần; và 100 client chỉ tạo 20 backend thật đúng bằng pool_size. Ba chế độ pooling và khi nào dùng chế độ nào. PostgreSQL 16.
22/09/2026
· 6 phút đọc
92
Prepared statement và plan cache: tái dùng kế hoạch, và cái bẫy generic plan trên dữ liệu lệch
Prepared statement bỏ qua parse và plan khi chạy lại, nhưng plan cache có một cái bẫy: generic plan dùng ước lượng trung bình. Đo thật trên bảng lệch 99% một giá trị: generic plan ước lượng 6329 dòng trong khi thực tế 1,98 triệu — sai 313 lần, chọn Index Scan chậm hơn Seq Scan của custom plan. Cơ chế 5 lần đầu và cách ép force_custom_plan. PostgreSQL 16.
22/09/2026
· 6 phút đọc
93
Khóa hàng, bảng và advisory trong PostgreSQL: ai chặn ai, đo bằng đồng hồ
PostgreSQL có ba tầng khóa: khóa hàng (FOR UPDATE) chỉ chặn cùng hàng, khóa bảng 8 mức mà ACCESS EXCLUSIVE của DDL chặn cả SELECT, và advisory lock do ứng dụng tự định nghĩa. Đo thật bằng hai session đồng thời: cùng đối tượng chờ 3 giây, đối tượng khác chạy ngay. Vì sao một ALTER TABLE có thể treo cả ứng dụng. PostgreSQL 16.
22/09/2026
· 6 phút đọc
94
Đọc pg_locks để tìm khóa chờ: ai đang chặn ai, đo trên sự cố thật
Khi một truy vấn treo vì khóa, pg_locks và pg_blocking_pids cho biết chính xác PID nào chặn PID nào. Đo trên sự cố thật: pg_blocking_pids trả về kẻ chặn, cây chặn hiện truy vấn bị chặn lẫn kẻ chặn kèm thời gian chờ, và pg_locks thô cho thấy granted=false của kẻ đang chờ. Cách gỡ bằng pg_cancel_backend. PostgreSQL 16.
22/09/2026
· 5 phút đọc
95
Deadlock trong PostgreSQL: phát hiện và tránh — kích một cái thật để xem
Deadlock xảy ra khi hai giao dịch giữ khóa mà bên kia cần, theo thứ tự ngược nhau. PostgreSQL tự phát hiện sau deadlock_timeout (1s) và hủy một nạn nhân với lỗi deadlock detected. Kích một deadlock thật để xem lỗi và log đầy đủ, rồi chứng minh khóa cùng thứ tự loại bỏ nó hoàn toàn. Vì sao ứng dụng phải có retry. PostgreSQL 16.
22/09/2026
· 5 phút đọc
96
SELECT FOR UPDATE và SKIP LOCKED: hàng đợi công việc không tranh chấp trong PostgreSQL
FOR UPDATE khóa hàng chủ động, nhưng nhiều worker cùng nhắm job đầu tiên sẽ nối đuôi chờ nhau. SKIP LOCKED bỏ qua hàng đang bị khóa để lấy hàng kế. Đo thật: FOR UPDATE chờ 3 giây, SKIP LOCKED lấy job kế trong 0,08 giây; 3 worker chạy cùng lúc lấy 3 job khác nhau không tranh chấp. Và NOWAIT để fail-fast. PostgreSQL 16.
22/09/2026
· 5 phút đọc
97
Isolation level trong PostgreSQL: ba mức cô lập và cái giá hiệu năng của chúng
PostgreSQL có ba mức cô lập giao dịch: Read Committed thấy dữ liệu mới nhất mỗi câu, Repeatable Read giữ ảnh chụp nhất quán, Serializable đảm bảo như chạy tuần tự. Đo thật hành vi và chi phí: Read Committed đọc lại thấy 200, Repeatable Read vẫn thấy 100; và Serializable chậm hơn ~21%. Vì sao mức cao cần logic thử lại. PostgreSQL 16.
22/09/2026
· 6 phút đọc
98
Giao dịch dài giữ bloat: vì sao một tab quên đóng làm phình cả bảng PostgreSQL
Một giao dịch mở lâu giữ xmin horizon, khiến VACUUM không xóa được dead tuple mới hơn nó — kể cả dead tuple do giao dịch khác tạo. Đo thật: bảng phình từ 3,5 MB lên 14 MB với 300k dead tuple không dọn được trong khi một giao dịch REPEATABLE READ còn mở; đóng nó thì VACUUM dọn sạch ngay. Cách tìm thủ phạm và phòng tránh. PostgreSQL 16.
22/09/2026
· 5 phút đọc
99
Phân vùng bảng trong PostgreSQL: range, list, hash — chọn kiểu nào cho dữ liệu nào
PostgreSQL có ba kiểu phân vùng: RANGE theo khoảng (ngày, số) cho dữ liệu thời gian, LIST theo giá trị rời rạc (miền, loại), HASH chia đều theo băm khóa. Đo thật: dòng tự định tuyến vào đúng vùng (RANGE 100k mỗi năm, HASH cân bằng ~100k mỗi vùng), và một truy vấn chỉ quét đúng partition liên quan. Khi nào dùng kiểu nào. PostgreSQL 16.
22/09/2026
· 5 phút đọc
100
Partition pruning trong PostgreSQL: bỏ vùng không cần, nhanh gấp 10 lần
Partition pruning là lý do chính để phân vùng: planner bỏ qua các vùng không thể chứa dữ liệu khớp. Đo thật trên bảng 5 triệu dòng 10 vùng: lọc theo khóa phân vùng chỉ quét 1 vùng (19,8 ms), không lọc theo khóa phải quét cả 10 vùng (207 ms) — nhanh gấp 10 lần. Plan-time vs run-time pruning và Subplans Removed. PostgreSQL 16.
22/09/2026
· 5 phút đọc
101
Khi nào nên phân vùng PostgreSQL: con dao hai lưỡi, đo trước khi chia
Phân vùng không phải luôn nhanh hơn. Đo thật: cùng 3 triệu dòng, truy vấn khớp khóa phân vùng nhanh gấp 5 lần, nhưng truy vấn point lookup theo cột khác lại chậm gấp 20 lần (phải dò index của cả 50 vùng) cộng planning đắt gấp 7 lần. Khi nào phân vùng đáng, khi nào chỉ thêm phức tạp. PostgreSQL 16.
22/09/2026
· 5 phút đọc
102
Partition-wise join và aggregate: xử lý theo từng cặp vùng, và vì sao chúng tắt mặc định
Khi hai bảng phân vùng giống nhau, PostgreSQL có thể join theo từng cặp vùng thay vì gộp cả bảng — hash table nhỏ hơn, song song hóa được. Đo thật: partition-wise join giảm số batch tràn đĩa từ 32 xuống 4; partition-wise aggregate nhanh hơn ~20%. Nhưng cả hai tắt mặc định. Khi nào bật. PostgreSQL 16.
22/09/2026
· 5 phút đọc
103
Quản lý partition theo thời gian: DROP thay DELETE, DETACH và DEFAULT partition
Phân vùng theo thời gian cần vòng đời: tạo partition tương lai trước, DEFAULT partition bắt dòng ngoài dải, và dọn dữ liệu cũ. Đo thật: DROP partition xóa cả tháng dữ liệu trong 0,069 giây với 0 dead tuple, còn DELETE tương đương để lại 303 nghìn dead tuple cần VACUUM. DETACH tách partition thành bảng độc lập. PostgreSQL 16.
22/09/2026
· 5 phút đọc
104
Index trên bảng phân vùng PostgreSQL: lan xuống, unique phải chứa khóa, và dựng không khóa
Index tạo trên bảng cha phân vùng tự lan xuống mọi partition và partition mới cũng thừa kế. Nhưng unique/primary key BẮT BUỘC chứa khóa phân vùng. Đo thật: unique thiếu khóa phân vùng báo lỗi, duplicate bị chặn ở mức partition, và cách dựng index không khóa dài bằng ON ONLY + CONCURRENTLY + ATTACH. PostgreSQL 16.
22/09/2026
· 5 phút đọc
105
Bảng rất lớn trong PostgreSQL: các đòn bẩy lưu trữ để bảng tỉ dòng vẫn chạy được
Bảng cực lớn cần kết hợp nhiều đòn bẩy lưu trữ. Đo thật: fillfactor=90 cho 43% HOT update (48 MB so với 59 MB), giảm bloat cho bảng ghi nặng; TOAST nén text lặp lại 42 lần và chuyển giá trị khó nén ra ngoài dòng (main chỉ 1 MB, TOAST 195 MB) để heap chính luôn hẹp. Cùng phân vùng và tablespace phân tầng. PostgreSQL 16.
22/09/2026
· 5 phút đọc
106
Sharding PostgreSQL: chia dữ liệu qua nhiều máy khi một máy không đủ
Khi ghi và dữ liệu vượt sức một máy, sharding chia bảng ngang qua nhiều máy chủ. Đo thật với postgres_fdw: bảng phân tán 1 triệu dòng qua 2 shard, truy vấn theo shard key chỉ route tới một máy (Foreign Scan), nhưng truy vấn theo cột khác fan-out tới mọi shard. Ba cách tiếp cận và vì sao chọn shard key là quyết định khó đảo ngược. PostgreSQL 16.
22/09/2026
· 5 phút đọc
107
COPY vs INSERT hàng loạt trong PostgreSQL: nạp dữ liệu nhanh gấp hàng chục lần
Nạp khối lượng lớn bằng INSERT từng dòng tự commit là sai lầm đắt nhất. Đo thật: 20.000 dòng qua INSERT autocommit mất 2,4 giây (fsync mỗi dòng) so với COPY 0,046 giây — nhanh 52 lần. Ở 200k dòng: COPY 29 ms so với INSERT từng dòng 290 ms. Vì sao COPY nhanh và cách dùng đúng cho ETL. PostgreSQL 16.
22/09/2026
· 5 phút đọc
108
Multi-row INSERT và batch trong PostgreSQL: kích thước lô tối ưu, lợi ích giảm dần
Gộp nhiều dòng vào một câu INSERT VALUES giảm round-trip mạng và chi phí lặp lại. Đo thật nạp 100k dòng: lô 1 dòng mất 8,49 giây, lô 50 chỉ 0,32 giây (nhanh 26 lần), nhưng từ lô 500 trở lên lợi ích giảm dần rõ rệt (0,16 so với 0,14 giây). Điểm ngọt là 500-1000 dòng mỗi lô. Khi nào dùng thay COPY. PostgreSQL 16.
22/09/2026
· 4 phút đọc
109
UPSERT trong PostgreSQL: ON CONFLICT làm chèn-hoặc-cập-nhật atomic, không dính race
INSERT ... ON CONFLICT gộp chèn và cập nhật vào một câu lệnh atomic — thay cho SELECT-rồi-ghi vừa chậm vừa dính race condition. Đo thật: UPSERT một câu nhanh 2,1 lần, mệnh đề WHERE xoá 50.000 dead tuple, và bẫy 'cannot affect row a second time'. PostgreSQL 16.
22/09/2026
· 8 phút đọc
110
Cập nhật và xoá hàng loạt trong PostgreSQL: vì sao chia lô chậm hơn mà vẫn nên làm
Một UPDATE/DELETE khổng lồ nhanh hơn về wall-clock nhưng khoá mọi dòng suốt cả giao dịch, dồn hàng trăm MB WAL một cục và phình bảng gần gấp đôi. Đo thật: UPDATE 2 triệu dòng sinh 722 MB WAL và 2 triệu dead tuple; chia lô kèm VACUUM giữ bloat thấp. PostgreSQL 16.
22/09/2026
· 6 phút đọc
111
Nạp dữ liệu lớn vào PostgreSQL: vì sao bỏ index rồi tạo lại nhanh gần gấp đôi
Mỗi index phải cập nhật theo từng dòng chèn, nên nạp khối lượng lớn vào bảng đã đầy index rất chậm. Đo thật: nạp 3 triệu dòng với 4 index sẵn mất 12,6 giây; bỏ index thứ cấp rồi tạo lại sau khi nạp chỉ 7 giây và bảng còn nhỏ hơn. Và một điểm bất ngờ về maintenance_work_mem. PostgreSQL 16.
22/09/2026
· 7 phút đọc
112
UNLOGGED TABLE trong PostgreSQL: nạp nhanh hơn nhờ bỏ WAL, và cái giá khi crash
Bảng UNLOGGED không ghi WAL nên nạp nhanh hơn và không tốn I/O ghi log. Đo thật: nạp 3 triệu dòng sinh 0 byte WAL so với 230 MB, nhanh ~1,4 lần. Nhưng crash một cái là bảng bị cắt sạch về rỗng — chứng minh bằng SIGKILL thật. PostgreSQL 16.
22/09/2026
· 6 phút đọc
113
TRUNCATE vs DELETE trong PostgreSQL: nhanh hơn 34 lần, và những khác biệt dễ vấp
TRUNCATE vứt cả tệp dữ liệu nên gần như tức thời và trả chỗ ngay; DELETE quét từng dòng, để lại dead tuple và bảng không nhỏ lại. Đo thật: làm rỗng bảng 3 triệu dòng mất 678 ms với DELETE so với 20 ms với TRUNCATE. Cùng khác biệt về giao dịch, sequence và trigger. PostgreSQL 16.
22/09/2026
· 6 phút đọc
114
Bảo trì PostgreSQL: ANALYZE, VACUUM và REINDEX làm gì, khi nào cần
Ba việc giữ database khoẻ: ANALYZE cập nhật thống kê cho planner, VACUUM dọn dead tuple, REINDEX dựng lại index phình. Đo thật: thống kê cũ khiến planner đoán sai 4 lần, VACUUM dọn 1 triệu dead tuple, và một index phình gấp 4 lần được REINDEX về như mới. PostgreSQL 16.
22/09/2026
· 6 phút đọc
115
Streaming replication trong PostgreSQL: dựng một bản sao nóng và đo nó chạy thật
Streaming replication đẩy WAL từ primary sang standby theo thời gian thực để có bản sao nóng chỉ đọc. Bài này dựng thật hai container, đo: 600 nghìn dòng nhân bản tức thì, replica từ chối ghi, và đánh đổi async vs sync. PostgreSQL 16.
22/09/2026
· 6 phút đọc
116
Read replica trong PostgreSQL: phân tải đọc để tăng thông lượng gấp nhiều lần
Đưa truy vấn đọc sang replica giải phóng primary và cộng thêm năng lực đọc theo chiều ngang. Đo thật trên hai node: đọc tách ra hai nơi cho tổng 76 nghìn tps, gấp 2,6 lần một node. Cùng cách định tuyến và cạm bẫy đọc phải dữ liệu cũ. PostgreSQL 16.
22/09/2026
· 5 phút đọc
117
Replication lag trong PostgreSQL: đo bằng byte và giây, và khi nó làm hỏng tính đúng đắn
Replication lag là khoảng cách giữa WAL primary đã ghi và replica đã phát lại. Bài này đo thật: tạm dừng replay để lag phình lên 66 MB và replica kẹt ở dữ liệu cũ trong khi primary có 501 nghìn dòng — minh hoạ đọc phải dữ liệu cũ. Cùng cách đo và giảm lag. PostgreSQL 16.
22/09/2026
· 6 phút đọc
118
Nhân bản đồng bộ vs bất đồng bộ trong PostgreSQL: đổi thông lượng lấy độ bền
Nhân bản sync bắt primary chờ standby xác nhận trước khi commit — không mất dữ liệu khi primary chết, nhưng chậm hơn. Đo thật: dải synchronous_commit từ 5021 xuống 2475 tps, và demo commit treo với wait_event SyncRep khi standby chết. PostgreSQL 16.
22/09/2026
· 6 phút đọc
119
Logical replication trong PostgreSQL: nhân bản theo bảng, không phải cả cụm
Logical replication dùng publisher/subscriber để nhân bản chọn lọc từng bảng, cho subscriber ghi được và chạy khác phiên bản. Bài này dựng thật lab sang sub_db: publish 2 trong 3 bảng, đo copy ban đầu, INSERT UPDATE DELETE nhân bản, và bảng không publish thì không sang. PostgreSQL 16.
22/09/2026
· 5 phút đọc
120
pg_stat_activity trong PostgreSQL: nhìn database đang chạy gì và ai chặn ai
pg_stat_activity là cửa sổ nhìn vào mọi phiên đang kết nối: query nào đang chạy, phiên nào chờ khoá, idle-in-transaction nào đang giữ khoá. Bài này dựng thật ba phiên xung đột, dùng pg_blocking_pids tìm thủ phạm, rồi pg_terminate_backend giải phóng. PostgreSQL 16.
22/09/2026
· 5 phút đọc
121
Wait events trong PostgreSQL: đọc hệ thống đang chờ ở đâu để tìm nút thắt thật
Wait events cho biết mỗi phiên đang chờ gì — khoá, ghi WAL, đọc đĩa, hay client. Lấy mẫu chúng nhiều lần là một profiler nghèo cho database. Bài này đo thật một tải ghi bị nghẽn ở WAL, một scan đọc đĩa, và một khoá hàng. PostgreSQL 16.
22/09/2026
· 5 phút đọc
122
pg_stat_user_tables trong PostgreSQL: tìm bảng thiếu index qua seq scan
pg_stat_user_tables đếm mỗi bảng bị quét tuần tự hay dùng index bao nhiêu lần. Bài này đo thật: một bảng 1 triệu dòng bị 20 truy vấn quét tuần tự đọc 20 triệu dòng, thêm index thì về 0 seq scan. Và vì sao 100% seq scan trên bảng nhỏ lại hoàn toàn bình thường. PostgreSQL 16.
22/09/2026
· 5 phút đọc
123
pg_stat_io của PostgreSQL 16: nhìn I/O chi tiết chưa từng có
pg_stat_io là view mới của PG16 tách I/O theo ai gây ra, trên cái gì, và vì sao. Bài này đo thật: một seq scan lớn dùng ring buffer (6,9% hit, 96 nghìn reuses thay vì evictions), extends khi bảng lớn dần, và I/O của VACUUM tách riêng. PostgreSQL 16.
22/09/2026
· 5 phút đọc
124
Tìm truy vấn chậm trong PostgreSQL: vì sao phải nhìn tổng thời gian, không phải mỗi lần
pg_stat_statements gom mọi lần chạy cùng dạng truy vấn. Bài này đo thật một nghịch lý: một point-lookup 0,008 mili giây chạy 427 nghìn lần chiếm 92,5 phần trăm tổng thời gian database, còn một full scan 94 mili giây chỉ chiếm 7,5 phần trăm — sắp theo mean sẽ đuổi nhầm thủ phạm. PostgreSQL 16.
22/09/2026
· 5 phút đọc
125
pgBadger: biến log PostgreSQL thành báo cáo hiệu năng, phân tích quá khứ
pgBadger đọc log PostgreSQL và sinh báo cáo trực quan về truy vấn chậm, giờ cao điểm và lỗi. Bài này dựng thật: cấu hình log, sinh traffic, chạy pgBadger 13.2 trên log 31 MB — báo cáo thật thấy 192 nghìn query gom thành 9 dạng, peak 45 nghìn query mỗi giây, và top query tốn tổng thời gian. PostgreSQL 16.
22/09/2026
· 5 phút đọc
126
Cần cảnh báo những gì trên PostgreSQL production: các chỉ số sống còn
Giám sát chỉ có ích khi cảnh báo đúng thứ, đúng lúc. Bài này đo thật trên một database các chỉ số cần đặt ngưỡng báo động: nguy cơ wraparound transaction ID (cảnh báo số một), kết nối gần cạn, giao dịch dài, replication lag, và cache hit ratio. PostgreSQL 16.
22/09/2026
· 6 phút đọc
127
Chẩn đoán một truy vấn chậm trong PostgreSQL từ A đến Z: quy trình 5 bước
Gộp mọi công cụ đã học vào một quy trình thực chiến: phát hiện, đọc kế hoạch, tìm nguyên nhân, sửa, xác nhận. Bài này chẩn đoán thật một truy vấn 49 mili giây quét cả 3 triệu đơn — tìm ra thiếu index, thêm index từng phần nhỏ 7,6 MB, còn 9,5 mili giây. PostgreSQL 16.
22/09/2026
· 6 phút đọc
128
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.
22/09/2026
· 5 phút đọc
129
Dashboard nhanh lúc vắng, chậm giờ cao điểm: tìm nút thắt dưới tải đồng thời
Một truy vấn chạy 25 mili giây khi test một mình lại thành 266 mili giây khi 32 người cùng mở dashboard. Bài này đo thật sự suy giảm theo số client, chẩn đoán bằng wait_event, và một cách sửa bất ngờ: tắt parallel query giúp nhanh 1,7 lần dưới tải cao. PostgreSQL 16.
22/09/2026
· 5 phút đọc
130
Migration lớn không downtime trong PostgreSQL: đổi schema an toàn trên bảng đang chạy
Đổi schema trên bảng production đang phục vụ mà không gây downtime — mấu chốt là mức khoá và thao tác có viết lại bảng không. Bài này đo thật: ADD COLUMN default tức thì, đổi kiểu cột khoá cả bảng, CREATE INDEX CONCURRENTLY không chặn ghi, và NOT VALID cho ràng buộc. PostgreSQL 16.
22/09/2026
· 6 phút đọc
131
Checklist tối ưu PostgreSQL: 5 nhóm cần rà để không bỏ sót
Một danh sách kiểm tra thực dụng gom cả series lại: cấu hình, index, truy vấn, bảo trì, giám sát. Bài này còn audit thật một database và phát hiện nhiều tham số vẫn ở mặc định chưa chỉnh theo phần cứng — đúng loại thứ checklist bắt được. PostgreSQL 16.
22/09/2026
· 5 phút đọc
132
Tối ưu PostgreSQL bền vững: một vòng lặp, không phải một lần chỉnh rồi quên
Bài chốt series: tối ưu không có đích đến mà là một chu trình đo-sửa-xác nhận-giám sát lặp lại. Kèm health snapshot thật của một database khoẻ mạnh và ba nguyên tắc xuyên suốt 132 bài — đo đừng đoán, hiểu đánh đổi, sửa gốc không vá ngọn. PostgreSQL 16.
22/09/2026
· 6 phút đọc