Một truy vấn chậm khiến bạn mở execution plan, thấy cost lớn rồi kết luận database mất từng ấy mili giây. Nếu bắt đầu từ cách đọc này, những lần so sánh sau rất dễ sai. Cost là ước lượng của planner; thời gian thực tế chỉ xuất hiện khi bạn chạy phép đo phù hợp.
Bài này hướng dẫn một vòng quan sát trước và sau khi thêm index trên bảng thử. Phạm vi tài liệu là PostgreSQL 17, đối chiếu ngày 24/10/2026. SQL đã được rà theo tài liệu nhưng chưa chạy trên PostgreSQL trong lượt biên tập; bài không cung cấp plan hoặc thời gian đo giả định.
Điều kiện cần có
Bạn cần một database thử nghiệm và quyền tạo bảng tạm. Chạy các lệnh trong cùng một phiên psql vì bảng tạm thuộc phiên đó. Không dùng dữ liệu production cho lần làm quen này. Ghi phiên bản bằng SELECT version(); cùng với kết quả plan để lần sau còn đối chiếu.
1. Tạo dữ liệu có thể làm lại
Fixture bên dưới tạo 10.000 hàng với 100 giá trị workspace. Đây là phân bố được dựng để học cách đọc plan, không đại diện cho tải thật của ứng dụng.
CREATE TEMP TABLE plan_demo (
id integer PRIMARY KEY,
workspace_id integer NOT NULL,
body text NOT NULL
);
INSERT INTO plan_demo (id, workspace_id, body)
SELECT n, n % 100, repeat('x', 100)
FROM generate_series(1, 10000) AS n;
ANALYZE plan_demo;
EXPLAIN
SELECT id
FROM plan_demo
WHERE workspace_id = 7
ORDER BY id
LIMIT 20;
ANALYZE cập nhật thống kê để planner ước lượng phân bố dữ liệu. Bạn có thể đọc thêm về mục đích của lệnh trong tài liệu ANALYZE. Việc gọi nó ở đây cũng giúp bạn không vô tình so sánh plan trước và sau index trong hai trạng thái thống kê khác nhau mà không ghi nhận.
2. Tách ước lượng khỏi việc thực thi
Theo hướng dẫn sử dụng EXPLAIN, cost=a..b là chi phí khởi động và tổng chi phí ước lượng bằng đơn vị nội bộ. rows là số hàng node dự kiến trả ra; width là kích thước hàng trung bình ước lượng. Tổng cost của node cha đã bao gồm phần việc bên dưới, vì vậy không cộng toàn bộ cây như hóa đơn độc lập.
Để lấy dữ liệu thực thi, chạy:
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM plan_demo
WHERE workspace_id = 7
ORDER BY id
LIMIT 20;
EXPLAIN ANALYZE thực sự chạy câu lệnh. Tham chiếu EXPLAIN mô tả các tùy chọn và tác động này. Với bài học, chỉ dùng SELECT trên bảng thử. Đừng thêm ANALYZE một cách vô thức trước UPDATE hay DELETE; tác dụng của câu lệnh vẫn xảy ra. Ngay cả SELECT trên hệ thống thật cũng có thể tốn tài nguyên hoặc gọi hàm có tác động riêng.
3. Đọc từ sai lệch cụ thể
Trên kết quả của bạn, chọn node truy cập bảng và đối chiếu rows ước lượng với actual rows. Sai lệch lớn gợi ý cần tìm hiểu thống kê hoặc đặc điểm dữ liệu trước khi áp dụng mẹo thêm index. Với node có loops lớn hơn một, số hàng và thời gian thực tế được hiển thị theo trung bình mỗi lần thực thi; đọc cả số vòng lặp mới thấy quy mô công việc.
Buffers giúp bạn nhìn hoạt động truy cập bộ đệm. shared hit không phải một lần đọc đĩa; shared read cũng không đủ để khẳng định thiết bị lưu trữ vật lý vừa được đọc, vì còn cache ở tầng hệ điều hành. Hãy lưu nguyên output để tránh làm mất bối cảnh bằng cách chụp riêng một con số.
Phần thống kê của planner giúp giải thích vì sao ước lượng phụ thuộc dữ liệu thống kê. Tuy nhiên, một lần thấy estimated rows khác actual rows chưa tự chứng minh nguyên nhân duy nhất là thiếu ANALYZE.
4. Thêm index rồi quan sát lại
CREATE INDEX plan_demo_workspace_id_idx
ON plan_demo (workspace_id, id);
ANALYZE plan_demo;
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM plan_demo
WHERE workspace_id = 7
ORDER BY id
LIMIT 20;
Index được chọn làm ứng viên vì truy vấn lọc theo workspace rồi lấy ID theo thứ tự. Nó không buộc planner phải dùng index scan. Giữ nguyên câu SELECT, lưu hai plan và ghi tình trạng cache cùng môi trường. Nếu không thấy cải thiện, hãy coi đó là kết quả cần giải thích, không sửa số liệu cho khớp kỳ vọng.
Kiểm tra và lỗi thường gặp
Đầu tiên xác nhận SELECT vẫn trả đúng dữ liệu cần lấy. Sau đó so sánh kiểu scan, việc sort, số hàng bị lọc và loops. Tách câu hỏi “plan có đổi không?” khỏi “độ trễ của luồng ứng dụng có giảm không?”. Fixture nhỏ trong một phiên không đo được ảnh hưởng của nhiều request đồng thời, mạng hay pool kết nối.
Đừng cộng thời gian cha và con vì chúng có phần chồng nhau. Đừng gọi mọi sequential scan là lỗi; với lượng dữ liệu và độ chọn lọc cụ thể, đó có thể là lựa chọn hợp lý. Cũng đừng lấy kết quả chạy lần hai sau khi cache nóng để khẳng định index đã tạo ra toàn bộ mức thay đổi.
Gợi ý tiếp theo
Chọn một truy vấn thật đã được làm sạch thông tin riêng tư, ghi tham số và plan trong môi trường thử đại diện hơn. Mục tiêu đầu tiên là giải thích được đường đi của dữ liệu và điểm ước lượng sai, rồi mới quyết định tối ưu. Cách đọc có bối cảnh hữu ích hơn việc săn một giá trị cost nhỏ.

