Đọc EXPLAIN
Bạn sẽ học được
- Đọc một dòng kế hoạch:
cost,rows,width,actual time,loops. - Phân biệt dự đoán của planner với số đo thực tế.
- Vì sao
EXPLAINkhácEXPLAIN ANALYZEvà khi nào không nên dùng bản sau. - Dùng
Buffersđể biết truy vấn đọc bao nhiêu trang. - Quy trình ba bước khi một truy vấn chậm.
Khái niệm cốt lõi
EXPLAIN trả về kế hoạch thực thi — cây các bước PostgreSQL dự định làm. EXPLAIN ANALYZE chạy truy vấn thật rồi in kèm số đo. Đây là công cụ đầu tiên phải mở khi ai đó nói "câu này chậm".
EXPLAIN SELECT * FROM orders WHERE customer_id = 2;Seq Scan on orders (cost=0.00..24.12 rows=6 width=44)
Filter: (customer_id = 2)Ba con số trong ngoặc:
| Thành phần | Ý nghĩa |
|---|---|
cost=0.00..24.12 | Chi phí ước lượng: khởi động..tổng. Đơn vị trừu tượng, không phải mili giây |
rows=6 | Số dòng planner đoán sẽ trả về |
width=44 | Số byte trung bình mỗi dòng |
Chi phí mặc định quy ước một lần đọc trang là 1.0, một phép so sánh dòng khoảng 0.01. Vì vậy 24.12 gần như nói "đọc khoảng 24 trang". Con số này chỉ dùng để so sánh các kế hoạch, không dùng để dự đoán thời gian.
Chạy bản có đo:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = 2;Seq Scan on orders (cost=0.00..24.12 rows=6 width=44) (actual time=0.013..0.020 rows=3.00 loops=1)
Filter: (customer_id = 2)
Rows Removed by Filter: 13
Buffers: shared hit=1
Planning Time: 0.023 ms
Execution Time: 0.089 msPhần thêm vào rất giàu thông tin:
actual time=0.013..0.020: mili giây đến dòng đầu và đến dòng cuối.rows=3.00: thực tế 3 dòng, planner đoán 6 — sai gấp đôi nhưng không quan trọng vì bảng nhỏ.loops=1: số lần nút này được thực thi. Với nút nằm trong vòng lặp,actual timelà trung bình mỗi vòng, phải nhân vớiloopsmới ra tổng.Rows Removed by Filter: 13: đọc 16 dòng, bỏ 13. Đây là dấu hiệu điều kiện không chọn lọc.Buffers: shared hit=1: đọc một trang, đã nằm trong bộ nhớ đệm.Planning TimevàExecution Timetách riêng: đôi khi truy vấn nhanh nhưng lập kế hoạch chậm.
Đọc một kế hoạch lớn
Kế hoạch trên bảng 200.000 dòng:
Aggregate (cost=2979.68..2979.69 rows=1 width=8) (actual time=9.793..9.794 rows=1.00 loops=1)
Buffers: shared hit=1274
-> Seq Scan on events (cost=0.00..2977.98 rows=682 width=0) (actual time=0.012..9.777 rows=40.00 loops=1)
Filter: (user_id = 42)
Rows Removed by Filter: 199960
Buffers: shared hit=1274
Planning Time: 0.063 ms
Execution Time: 9.819 msCách đọc từ trong ra ngoài, từ dưới lên trên:
Seq Scan on eventslà nút lá — đọc toàn bảng.- Mũi tên
->cho biết nút cha.Aggregatenhận dòng từ con và đếm. actual time=9.777của nút con gần bằngExecution Time: 9.819của cả câu → gần như toàn bộ thời gian nằm ở bước quét bảng.
Hai chỉ số quyết định chẩn đoán:
Rows Removed by Filter: 199960 trên tổng 200.000 dòng nghĩa là bộ lọc loại 99,98% dữ liệu. PostgreSQL đã làm việc rất nhiều để lấy rất ít. Đây là dấu hiệu rõ ràng cần index.
Buffers: shared hit=1274 nghĩa là đọc 1.274 trang — khoảng 10 MB. Dù tất cả đã nằm trong cache (hit), vẫn tốn thời gian CPU để kiểm tra từng dòng.
Sau khi thêm index, cùng câu chuyển thành:
Aggregate (cost=1282.62..1282.63 rows=1 width=8) (actual time=0.066..0.067 rows=1)
Buffers: shared hit=40 read=2
-> Bitmap Heap Scan on events (actual time=0.020..0.056 rows=40.00 loops=1)
Recheck Cond: (user_id = 42)
-> Bitmap Index Scan on idx_events_user (cost=0.00..11.79 rows=1000 width=0)
Index Cond: (user_id = 42)
Execution Time: 0.082 msBuffers giảm từ 1.274 xuống 42, thời gian từ 9,8 ms xuống 0,08 ms. Chú ý read=2: hai trang phải đọc từ đĩa, phần còn lại đã trong cache. Trong lần chạy đầu tiên, read sẽ cao hơn — luôn chạy lại lần hai trước khi kết luận về tốc độ.
Dự đoán sai — dấu hiệu quan trọng nhất
rows=682 dự đoán so với rows=40.00 thực tế là sai 17 lần. Planner dùng thống kê để đoán số dòng; thống kê cũ hoặc thiếu histogram khiến nó chọn sai phương án. Ba nguyên nhân thường gặp:
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
rows ước lượng lệch xa actual | Thống kê cũ | ANALYZE bảng; |
Planner chọn Nested Loop và chạy rất lâu | Ước lượng quá ít dòng | ANALYZE, xem xét index |
| Ước lượng đúng nhưng vẫn chậm | Kế hoạch hợp lý, dữ liệu lớn | Giảm dữ liệu đọc, phân trang |
Trong môi trường học, mỗi bài chạy trên một cơ sở dữ liệu mới nên thống kê luôn mới. Trên production, sau khi nhập hàng loạt dữ liệu, chạy ANALYZE là bước bắt buộc — nếu không, planner vẫn tưởng bảng chỉ có vài trăm dòng.
EXPLAIN ANALYZE có tác dụng phụ
EXPLAIN ANALYZE thực thi câu lệnh. Với SELECT, điều đó vô hại. Với UPDATE, DELETE, INSERT, nó thay đổi dữ liệu thật. Cách an toàn:
BEGIN;
EXPLAIN ANALYZE DELETE FROM orders WHERE status = 'cancelled';
ROLLBACK;Trong PostgreSQL hiện đại còn có EXPLAIN (ANALYZE, BUFFERS, WAL) cho câu ghi, nhưng thói quen bọc transaction rồi rollback vẫn đáng giữ.
Quy trình ba bước
Khi nhận báo cáo "truy vấn chậm":
- Đo —
EXPLAIN (ANALYZE, BUFFERS)để biết thời gian thật và số trang đọc. - Tìm nút đắt nhất — nút có
actual timelớn nhất, hoặcRows Removed by Filterlớn nhất. - Sửa đúng nguyên nhân — index nếu quét nhiều, viết lại điều kiện nếu bọc hàm, giảm cột nếu
widthlớn, phân trang nếu trả quá nhiều dòng.
Đừng đoán. Kế hoạch thật thường khác trực giác: một truy vấn "trông nặng" có thể nhanh vì dùng index, còn một truy vấn "trông nhẹ" có thể chậm vì Nested Loop nhân số dòng.
Thực hành
- Chạy
EXPLAINvàEXPLAIN (ANALYZE, BUFFERS)cho cùng một câu trênorders. Ghi lại bốn con số khác nhau giữa hai bản. - Trên bảng
events200.000 dòng, tìm nút cóRows Removed by Filterlớn nhất và giải thích. - Chạy
ANALYZE events;rồi so sánhrows=ước lượng trước và sau. - Đọc kế hoạch của
SELECT count(*) FROM events;và cho biết nó có đọc bảng không. - Viết một câu
UPDATEbọc trongBEGIN ... ROLLBACKcùngEXPLAIN ANALYZEvà xác nhận dữ liệu không đổi.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
actual time không khớp Execution Time | Đọc nút con, không cộng dồn | Cộng actual time × loops theo cây |
| Lần đầu chậm, lần sau nhanh | Cache đĩa | Chạy lại hoặc so sánh Buffers read |
| Kế hoạch đổi giữa hai lần chạy | Thống kê hoặc tham số khác | Dùng cùng dữ liệu, ANALYZE trước |
EXPLAIN ANALYZE DELETE xoá mất dữ liệu | Bản ANALYZE thực thi thật | Bọc transaction và rollback |
rows dự đoán lệch xa thực tế | Thống kê cũ | ANALYZE |
Chỉ nhìn cost để kết luận | Cost là đơn vị trừu tượng | Dùng actual time và Buffers |
Tóm tắt
EXPLAINlà kế hoạch dự kiến;EXPLAIN ANALYZEchạy thật và thêm số đo.costlà đơn vị so sánh, không phải mili giây;actual timemới là thời gian.Rows Removed by Filterlớn là dấu hiệu cần index hoặc viết lại điều kiện.Bufferscho biết số trang đọc — chỉ số ổn định hơn thời gian.- Luôn kiểm tra
rowsdự đoán so với thực tế; lệch xa nghĩa là thống kê cần cập nhật.
Dùng ← → để chuyển bài