Tổng hợp 71 câu hỏi phỏng vấn PostgreSQL kèm đáp án chi tiết, song ngữ Việt–Anh — sắp xếp từ cơ bản đến nâng cao. Ôn luyện miễn phí, tự động lưu tiến độ.
PostgreSQL là relational database (cơ sở dữ liệu quan hệ) mã nguồn mở — dữ liệu nằm trong các bảng có cột rõ ràng và ràng buộc chặt chẽ. Điểm mạnh chính: SQL chuẩn, transaction ACID (làm là đúng và đủ, không nửa vời), nhiều…
Cả ba đều là ràng buộc nhưng phục vụ mục đích khác nhau: - Primary key: định danh duy nhất mỗi dòng (mỗi user một id). Đây là cột mà các bảng khác thường trỏ tới. - Unique key: đảm bảo một (hoặc vài) cột…
NULL nghĩa là "không biết / chưa có", chứ không phải chuỗi rỗng, số 0 hay false. Vì là "không biết" nên so sánh = NULL luôn cho kết quả không xác định — phải dùng IS NULL / IS NOT NULL. Một bẫy hay…
Transaction gom nhiều câu lệnh thành một khối "được ăn cả, ngã về không": commit thì mọi thay đổi được ghi bền vững, rollback thì huỷ sạch như chưa từng chạy. Trong lúc chưa commit, thay đổi chưa hiện ra như dữ liệu hoàn chỉnh…
Dễ lẫn vì một số constraint tự tạo index, nhưng vai trò khác nhau: constraint là quy tắc dữ liệu (primary key, foreign key, unique, check, not null), còn index là cấu trúc giúp tìm/đọc nhanh hơn. Một unique constraint thường tạo unique index bên…
Index giống mục lục cuối sách: thay vì lật từng trang để tìm một từ, database nhảy thẳng tới đúng dòng. Nhờ đó các query lọc (WHERE), join hay sắp xếp (ORDER BY) nhanh hơn hẳn, và việc tìm theo giá trị duy nhất gần…
PostgreSQL có nhiều loại index, mỗi loại hợp với một kiểu dữ liệu/truy vấn: - B-tree (mặc định): tốt cho so sánh bằng, khoảng (<, , BETWEEN) và sắp xếp trên giá trị thường (số, chuỗi, ngày). - GIN: cho dữ liệu "nhiều giá trị…
Trong composite index (index nhiều cột) kiểu B-tree, thứ tự cột rất quan trọng — giống danh bạ sắp theo "họ rồi tên": tra theo họ rất nhanh, nhưng tra chỉ theo tên thì gần như vô dụng. Index (tenantid, status, createdat) phục vụ tốt…
Partial index chỉ index những dòng thoả một điều kiện, thay vì cả bảng. Rất hợp khi query thường chỉ quan tâm một phần nhỏ: bản ghi đang active, job chưa xử lý, hay dòng chưa bị xoá mềm. Ví dụ ép email duy nhất,…
Isolation level quyết định một transaction "nhìn thấy" công việc đang chạy dở của transaction khác đến đâu. PostgreSQL có ba mức thực tế (Read Uncommitted bị xử lý như Read Committed): - Read Committed (mặc định): mỗi câu lệnh thấy snapshot dữ liệu mới…
SELECT ... FOR UPDATE khoá đúng các dòng vừa chọn, để transaction khác không thể update/delete chúng cho đến khi bạn commit hoặc rollback. Nó dành cho tình huống "đọc — sửa — ghi" cần an toàn, ví dụ trừ tồn kho hay lấy job…
Ba cách tạo khoá chính tự sinh: - serial: cách viết tắt cũ, ngầm tạo một sequence. Còn chạy được nhưng không còn là cách khuyến nghị. - GENERATED ... AS IDENTITY: cách chuẩn SQL, rõ ràng hơn, để tạo số tự tăng — nên…
json lưu nguyên văn bản JSON và phân tích lại mỗi lần dùng. jsonb lưu dạng nhị phân đã phân tích sẵn — mất định dạng và thứ tự key gốc, nhưng query và đánh index nhanh hơn nhiều. Hầu hết ứng dụng nên dùng…
Chọn normalize (tách thành cột/bảng riêng) khi field cần join, hay lọc, cần ràng buộc/foreign key, cần update độc lập hoặc dùng cho báo cáo. Chọn JSONB khi cấu trúc hay đổi, ít khi query sâu, metadata thay đổi liên tục, hoặc cần giữ nguyên…
Generated column là cột mà giá trị được tự tính từ các cột khác, nên bạn không phải lặp lại logic đó ở tầng app và có thể index/query trực tiếp trên nó. Hợp với công thức cố định, thuộc về dữ liệu: họ tên…
JOIN ghép dòng giữa hai bảng; khác nhau ở chỗ có giữ lại dòng không khớp hay không: - INNER JOIN: chỉ giữ dòng khớp ở cả hai bảng. - LEFT JOIN: giữ toàn bộ bảng trái, bên phải không khớp thì điền NULL. -…
Window function tính toán dựa trên một nhóm dòng liên quan nhưng không gộp các dòng lại như GROUP BY — bạn vẫn giữ từng dòng và có thêm cột tính toán. Hợp với xếp hạng, cộng dồn (running total), trung bình trượt, khử trùng…
ANALYZE thu thập thống kê về phân bố dữ liệu (cột này có bao nhiêu giá trị khác nhau, giá trị nào hay gặp...). Planner dựa vào đó để đoán mỗi bước query trả về bao nhiêu dòng, rồi chọn cách chạy. Nếu thống kê…
Mỗi kết nối tới PostgreSQL là một process riêng khá nặng (tốn RAM). Nếu app mở quá nhiều kết nối, DB có thể cạn bộ nhớ, CPU bận chuyển ngữ cảnh và latency xấu đi. Connection pool giải quyết bằng cách giữ sẵn một số…
Migration ít downtime cốt ở chỗ tránh các thao tác khoá bảng lâu hoặc viết lại cả bảng vào giờ cao điểm. Pattern an toàn theo nhiều bước: thêm cột nullable → backfill dữ liệu theo từng lô nhỏ → deploy code app đọc/ghi tương…
Soft delete = không xoá thật mà đánh dấu, thường bằng cột deletedat. Hệ quả: gần như mọi query phải thêm WHERE deletedat IS NULL, và điều này ảnh hưởng tới index lẫn unique constraint — vì một email "đã xoá" về lý thuyết có…
UPSERT = "update nếu đã có, insert nếu chưa". Trong PostgreSQL viết bằng INSERT ... ON CONFLICT: khi câu insert đụng một unique constraint/index, bạn bảo nó update thay vì báo lỗi (hoặc bỏ qua). EXCLUDED là dòng đáng lẽ được insert. Cần chỉ rõ…
RETURNING cho phép INSERT, UPDATE, DELETE, MERGE trả về luôn các dòng vừa bị tác động, khỏi cần query lại lần nữa. Rất tiện để lấy id vừa sinh, ghi log dòng vừa đổi, hoặc cập nhật xong trả thẳng response cho client. Lợi ích…
CTE (mệnh đề WITH) cho phép đặt tên cho một subquery, để chẻ một query phức tạp thành từng bước dễ đọc — hoặc dùng kèm câu lệnh sửa dữ liệu. PostgreSQL hiện đại (từ bản 12) thường tự "inline" CTE để tối ưu, nhưng…
Enum trong PostgreSQL giới hạn một cột chỉ nhận một tập giá trị cố định và làm schema tự mô tả (nhìn là biết có những trạng thái nào). Hợp với tập trạng thái ổn định, ít đổi như order status. Nhưng enum khó "tiến…
GROUP BY chỉ sinh ra dòng cho dữ liệu đã tồn tại — tháng không có đơn thì không có dòng nào để gom. Cách chuẩn là tự sinh dải thời gian rồi LEFT JOIN dữ liệu vào. Ba chi tiết quyết định đúng/sai: -…
Expression index (index trên biểu thức) đánh index lên kết quả của một biểu thức thay vì giá trị cột thô. Nó giải quyết đúng tình huống mà điều kiện bắt buộc phải bọc cột trong hàm. Quy tắc khớp: biểu thức trong WHERE phải…
Mỗi dòng trong plan là một node, thụt lề sâu hơn là node con chạy trước. Đọc từ trong ra ngoài. - cost=start..total là ước lượng của planner theo đơn vị nội bộ (không phải mili giây). Số đầu là chi phí tới khi trả…
Ba node này là ba cách khác nhau để lấy dòng ra khỏi bảng, khác nhau ở cách truy cập đĩa. Seq Scan — đọc toàn bộ bảng tuần tự theo thứ tự trang. Không cần index. Nhanh nhất khi phải lấy phần lớn bảng,…
CREATE INDEX thường lấy khoá SHARE trên bảng — chặn mọi INSERT/UPDATE/DELETE cho tới khi index dựng xong. Với bảng lớn việc này kéo dài hàng chục phút, tương đương ngừng ghi. Dùng CONCURRENTLY: Cách này quét bảng hai lượt và chờ các transaction cũ…
PostgreSQL không sửa dòng tại chỗ. Mỗi UPDATE ghi một phiên bản mới của dòng (tuple mới) và đánh dấu phiên bản cũ là hết hiệu lực. DELETE chỉ đánh dấu, không xoá byte ngay. Mỗi tuple mang hai cột hệ thống: - xmin —…
Dead tuple là phiên bản dòng đã bị UPDATE thay thế hoặc DELETE đánh dấu, nhưng không transaction nào còn cần nhìn thấy nữa. Nó vẫn nằm trong file dữ liệu cho tới khi VACUUM đánh dấu vùng đó tái sử dụng được. Bloat là…
Autovacuum là tiến trình nền tự động chạy hai việc trên từng bảng: 1. VACUUM — thu hồi không gian của dead tuple để tái sử dụng, cập nhật visibility map, và đẩy lùi ngưỡng transaction ID wraparound. 2. ANALYZE — cập nhật thống kê…
Hai lệnh giải quyết hai việc khác nhau. VACUUM (và autovacuum): đánh dấu không gian dead tuple là tái sử dụng được cho chính bảng đó. File trên đĩa hầu như không nhỏ lại. Chỉ lấy SHARE UPDATE EXCLUSIVE — SELECT, INSERT, UPDATE, DELETE vẫn…
B-tree thường không giúp gì cho việc tìm bên trong document. jsonb cần GIN index, và chỉ một số toán tử mới dùng được nó. - @ (containment), ?, ?, ?& (tồn tại key) → dùng được GIN mặc định (jsonbops). - jsonbpathops nhỏ và…
Dùng declarative range partitioning theo cột thời gian, mỗi tháng (hoặc mỗi tuần nếu lượng ghi lớn) một partition. Lợi ích chính không phải là truy vấn nhanh hơn, mà là vòng đời dữ liệu: xoá dữ liệu cũ bằng drop table requestlogs202601 chạy tức…
PostgreSQL dùng mô hình process-per-connection: mỗi kết nối là một process hệ điều hành riêng, không phải thread. Chi phí đi kèm mỗi kết nối: - Bộ nhớ riêng của backend process: workmem cho mỗi thao tác sort/hash, tempbuffers, catalog cache, plan cache. Một kết…
Streaming replication là replication vật lý: primary ghi thay đổi vào WAL, standby mở một kết nối tới primary và nhận stream WAL liên tục, rồi replay từng record để giữ bản sao byte-level giống hệt. Các trạng thái vị trí WAL trên standby, theo…
Physical (streaming) replication copy WAL ở mức byte: standby là bản sao y hệt toàn bộ cluster, cùng phiên bản major, không ghi được, không chọn được bảng. Logical replication giải mã WAL thành thay đổi ở mức dòng (INSERT/UPDATE/DELETE) rồi gửi tới subscriber theo…
pgstatstatements là extension gom thống kê tích luỹ theo từng dạng truy vấn — nó chuẩn hoá tham số nên where id = 1 và where id = 2 được tính chung. (Extension cần có trong sharedpreloadlibraries, tức phải restart một lần.) Tìm truy vấn…
Hai tham số chặn hai kiểu sự cố khác nhau. statementtimeout — huỷ một câu lệnh chạy quá lâu. Bảo vệ khỏi truy vấn lỗi plan, quét toàn bảng ngoài ý muốn, hay báo cáo nặng lọt vào giờ cao điểm. idleintransactionsessiontimeout — ngắt session…
Cả ba đều là row-level lock lấy tường minh trong một SELECT, khác nhau ở mức độ chặt và ở việc chặn ai. - FOR UPDATE — chặt nhất. Giữ hàng để chính bạn sửa hoặc xoá; mọi transaction khác muốn FOR UPDATE, FOR SHARE,…
Với SELECT ... FOR UPDATE không kèm tuỳ chọn, mọi worker cùng nhắm hàng cũ nhất nên chỉ một worker chạy, số còn lại xếp hàng chờ đúng hàng đó. Hai tuỳ chọn thay đổi hành vi chờ: - SKIP LOCKED — bỏ qua các…
Ba tham số cắt ba nguyên nhân treo khác nhau: - statementtimeout — huỷ một câu lệnh chạy quá lâu. Chặn truy vấn nặng ngoài dự kiến chiếm tài nguyên. - locktimeout — huỷ câu lệnh nếu chờ khoá quá lâu, còn thời gian chạy…