Bỏ qua để đến nội dung
Knowledge Hub

Database (PostgreSQL) Cơ bản

Khi một endpoint chậm, câu hỏi đầu tiên không phải là “nên tạo index nào?”. Cần biết PostgreSQL đang đọc dữ liệu ra sao, đã chọn query plan nào và có đang chờ transaction khác hay không. Ba vấn đề này có thể cho cùng một triệu chứng ở ứng dụng, nhưng cách xử lý khác nhau.

Trang này là điểm bắt đầu để chọn đúng bài cần đọc. Danh sách đầy đủ nằm ở menu bên trái.

  1. Dữ liệu được lưu ở đâu? Bảng thông thường dùng heap, gồm các page chứa phiên bản vật lý của row (tuple). Giá trị lớn có thể được nén hoặc chuyển sang TOAST. Đọc page, heap, tuple và TOAST nếu cần hiểu vì sao kích thước bảng, kích thước row và lượng dữ liệu phải đọc không giống nhau.
  2. PostgreSQL lấy dữ liệu bằng cách nào? Planner ước lượng chi phí của các phương án rồi chọn một plan: quét bảng, đi qua index, kết hợp nhiều bảng bằng join và các bước khác. Có index không đồng nghĩa plan sẽ dùng nó. Bắt đầu bằng B-tree index rồi học cách đọc EXPLAIN và EXPLAIN ANALYZE để nhìn thấy lựa chọn thực tế.
  3. Khi nhiều transaction cùng sửa dữ liệu thì sao? Ngoài plan truy vấn, thời gian chờ lock có thể làm request chậm. Đọc row-level locks để phân biệt đọc thông thường với thao tác cần khoá row; nếu các transaction chờ nhau thành vòng, xem deadlock.

Đây là ba góc nhìn, không phải ba bước mà mọi câu SQL đều thực hiện: một câu SELECT thông thường không tự lấy row lock, và một plan dùng index có thể không phải đọc heap cho mọi row.

Đang tải diagram…

Tình huống đang gặp Đọc theo thứ tự
Mới làm quen PostgreSQL Cách dữ liệu được lưu → B-tree index → Đọc execution plan
Đã tạo index nhưng vẫn Seq Scan Đọc execution plan → Vì sao index không được dùng → Statistics và ANALYZE
Thiết kế index cho query nhiều điều kiện Thứ tự cột composite → Partial, expression, covering → GIN, GiST, BRIN
Join chậm hoặc trang sâu chậm Join algorithms, OFFSET và keyset
Request chậm khi nhiều giao dịch cùng ghi, lỗi 40P01 Row-level locks → Deadlock → NOWAIT và SKIP LOCKED
Dữ liệu đọc ra không nhất quán, nghi anomaly ACID → Transaction anomalies → Isolation levels
Bảng phình, VACUUM không kịp, cảnh báo wraparound MVCC → VACUUM, autovacuum và bloat → Transaction ID wraparound
Bảng rất lớn, cần xoá dữ liệu cũ theo kỳ Partitioning
Migration làm API treo, pool cạn vì chờ lock Table-level locks → Lock queue → Timeout và quan sát lock
Hai người sửa cùng bản ghi, cron chạy trùng trên nhiều pod Optimistic vs pessimistic, Advisory lock
too many clients, nhiều pod cạn connection Connection pooling và PgBouncer
Vừa ghi xong đọc lại thấy dữ liệu cũ Replication và read replica
Lỗi 23505 khi hai request cùng thêm một key Upsert với ON CONFLICT
Báo cáo lệch ngày, tổng tiền lệch vài đồng Kiểu dữ liệu dễ dùng sai
Chọn khoá chính: bigint hay UUID UUID hay bigint làm khoá
Khách hàng SaaS không bao giờ được thấy dữ liệu của nhau Row-level security và multi-tenancy

Partitioning mạnh nhất ở retention (DETACH/DROP partition) và bảo trì theo từng phần, chứ không mặc định nhanh hơn một index tốt. Đối chiếu plan và chi phí vận hành trước khi chọn.

  1. Nền tảng: storage layout, B-tree, EXPLAIN.
  2. Tối ưu query: index không được dùng, statistics, composite index, join, pagination.
  3. Đồng thời và giao dịch: ACID, MVCC, isolation levels, row-level locks, table-level locks, lock queue, optimistic vs pessimistic, advisory lock, deadlock, timeout và quan sát lock.
  4. Vận hành: VACUUM và bloat, transaction ID wraparound, partitioning, connection pooling, replication, upsert, kiểu dữ liệu, UUID hay bigint.

Chạy toàn bộ đoạn SQL sau trong cùng một phiên psql trên PostgreSQL 17. Bảng tạm chỉ tồn tại trong phiên đó, không thay đổi bảng ứng dụng:

CREATE TEMP TABLE demo_orders AS
SELECT id, id % 100 AS customer_id
FROM generate_series(1, 10000) AS g(id);
CREATE INDEX ON demo_orders (customer_id);
ANALYZE demo_orders;
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM demo_orders WHERE customer_id = 42;

Nhìn vào node quét dữ liệu: PostgreSQL chọn Seq Scan, Index Scan hay Bitmap Heap Scan? So rows ước lượng với actual rows, rồi xem Buffers để biết các page được truy cập. Plan có thể khác giữa các máy hoặc sau khi dữ liệu thay đổi; mục tiêu của bài thử là đọc điều PostgreSQL đã làm, không phải ép nó phải dùng index. Bài EXPLAIN giải thích từng trường trong output và cách điều tra khi số ước lượng lệch số thực tế.