Bài trước pg_stat_statements cho ta bức tranh tổng thể: truy vấn nào tốn nhiều thời gian tích luỹ. Nhưng nó có điểm mù: nó chỉ lưu trung bình, và gộp mọi tham số thành một khuôn. Khi một truy vấn thỉnh thoảng chậm đột biến — với một tham số cụ thể, vào một thời điểm cụ thể — bạn cần bắt tận tay câu đó, kèm nguyên văn giá trị. Công cụ cho việc này là log_min_duration_statement.

Một ngưỡng, tự động ghi mọi câu vượt

Ý tưởng rất đơn giản: đặt một ngưỡng thời gian (mili-giây), và PostgreSQL sẽ tự ghi vào log mọi câu lệnh chạy lâu hơn thế — kèm thời gian thật và nguyên văn câu lệnh với tham số cụ thể.

Ảnh chụp mã SQL nền tối cấu hình log_min_duration_statement. Chú thích ghi log mọi câu chạy lâu hơn ngưỡng mili-giây, trừ 1 là tắt, 0 là ghi tất cả. Cách 1 toàn hệ thống không cần khởi động lại chỉ reload, ALTER SYSTEM SET log_min_duration_statement bằng 200 nghĩa lớn hơn 200 ms thì ghi, SELECT pg_reload_conf. Cách 2 chỉ một phiên đo nhanh khi gỡ lỗi, SET log_min_duration_statement bằng 50. Cách 3 chỉ một user hoặc một database khoanh vùng không ồn cả hệ thống, ALTER ROLE app_user SET log_min_duration_statement bằng 200. Rồi chạy hai câu để so, SELECT count sao FROM pgbench_accounts WHERE abalance lớn hơn 500 chậm sẽ vào log, SELECT abalance FROM pgbench_accounts WHERE aid bằng 12345 nhanh sẽ bỏ qua

Hình 1: Ba cách đặt ngưỡng, tuỳ phạm vi. Toàn hệ thống (ALTER SYSTEM + pg_reload_conf() — không cần khởi động lại). Một phiên (SET, tiện khi đang gỡ lỗi). Một user/database (ALTER ROLE ... SET — khoanh vùng để không làm log ồn cả hệ thống). Giá trị -1 tắt hẳn, 0 ghi mọi câu (chỉ dùng khi debug ngắn — rất ồn).

Log thật: câu chậm bị ghi, câu nhanh thì không

Đặt ngưỡng 50 ms, chạy một câu chậm và một câu nhanh, rồi đọc log của PostgreSQL:

Ảnh chụp log thật của PostgreSQL nền tối với ngưỡng 50 ms. Lệnh docker logs pg-lab sau khi chạy 2 truy vấn. Dòng log 2026-09-22 05:42:48 UTC tiến trình 162 LOG duration 98.203 ms màu đỏ statement SELECT count sao FROM pgbench_accounts WHERE abalance lớn hơn 500. Chú thích câu chậm 98 ms lớn hơn 50 ms bị ghi lại nguyên văn kèm thời gian thật. Câu nhanh aid bằng 12345 nhỏ hơn 1 ms không có dòng nào dưới ngưỡng bỏ qua, ghi rõ không có dòng log cho truy vấn nhanh màu xanh. Chú thích không cần ngồi canh đặt ngưỡng một lần PostgreSQL tự tố cáo mọi câu vượt, kết hợp với pg_stat_statements bài trước thống kê tổng thể cộng bắt tận tay ca cá biệt

Hình 2: Thật. Câu SELECT count(*) ... WHERE abalance > 500 chạy 98,203 ms — vượt ngưỡng 50 ms nên vào log với đúng chữ duration: 98.203 ms statement: ... kèm nguyên văn câu lệnh. Câu tra aid = 12345 chạy dưới 1 ms nên không có dòng log nào. Bạn không phải ngồi canh — PostgreSQL tự tố cáo mọi câu vượt ngưỡng.

Vì sao nó bổ sung cho pg_stat_statements

Hai công cụ trả lời hai câu hỏi khác nhau, dùng chung mới đủ:

pg_stat_statements log_min_duration_statement
Cho biết Truy vấn nào tốn tổng nhiều nhất Lần chạy cụ thể nào chậm
Lưu gì Số liệu gộp theo khuôn Nguyên văn câu + tham số thật
Thấy đột biến? Khó (chỉ trung bình + stddev) Có — bắt đúng lần chậm
Có tham số? Không (gộp) Có (giá trị cụ thể)

Ví dụ điển hình: một truy vấn trung bình 5 ms nhưng với một customer_id có cực nhiều dữ liệu thì mất 3 giây. pg_stat_statements chỉ thấy trung bình 5 ms (không báo động); log_min_duration_statement bắt được đúng lần 3 giây kèm customer_id gây họa.

Đặt ngưỡng bao nhiêu, và cái bẫy

  • Production: thường 200–1000 ms — đủ cao để chỉ bắt câu thật sự chậm, không làm log ngập.
  • Gỡ lỗi: hạ tạm xuống 50 ms hoặc thấp hơn trong một phiên/một user, rồi trả lại.
  • Cạm bẫy log_min_duration_statement = 0: ghi mọi câu, kể cả hàng trăm nghìn câu nhanh — làm phình đĩa log và tự nó làm chậm hệ thống (ghi log cũng tốn I/O). Chỉ dùng trong vài phút khi thật cần.
  • log_statement = 'all' khác hẳn: nó ghi mọi câu bất kể thời gian và không kèm thời gian chạy — không phải thứ bạn muốn để tìm câu chậm.

Đọc log ở đâu và cùng ai

Dòng log đi tới đích cấu hình bởi log_destination (stderr, csvlog, hoặc syslog). Trên nhiều hệ thống, bật logging_collector = on để PostgreSQL tự xoay file log trong thư mục log/. Vài tuỳ chọn đi kèm đáng bật:

  • log_line_prefix — thêm thời gian, PID, user, database, application vào đầu mỗi dòng để lần ra ai chạy câu chậm.
  • log_lock_waits = on — ghi khi một câu chờ khóa quá lâu (một nguồn chậm không thấy trong thời gian thực thi thuần).
  • Công cụ pgBadger (bài sau) đọc những dòng log này và dựng báo cáo trực quan.

Ba ý mang về

  1. log_min_duration_statement tự động ghi mọi câu vượt ngưỡng — đã thấy thật: câu 98 ms vào log kèm nguyên văn, câu <1 ms thì không. Đặt bằng ALTER SYSTEM + pg_reload_conf(), hoặc SET/ALTER ROLE để khoanh phạm vi.
  2. Nó bổ sung cho pg_stat_statements: cái kia cho tổng thể và trung bình, cái này bắt lần chậm cá biệt kèm tham số thật — thứ cần để tái hiện và gỡ.
  3. Chọn ngưỡng vừa phải (200–1000 ms cho production); tránh = 0 vì ghi mọi câu làm phình log và tự làm chậm. Bật thêm log_line_prefix và log_lock_waits để có ngữ cảnh.

Log cho bạn câu nào chậm và bao lâu. Nhưng để biết vì sao chậm, bạn cần kế hoạch của chính lần chạy đó. Phần sau dùng auto_explain để tự động đính kèm EXPLAIN (thậm chí ANALYZE) vào log cho những truy vấn chậm — bắt được cả kế hoạch thật của ca cá biệt mà không cần chạy lại.