Trong mỗi lần triển khai, luôn có một vài truy vấn chạy quá chậm.
Đọc tiếp để biết cách khám phá các truy vấn mất quá nhiều thời gian để thực thi và cách tìm ra lý do tại sao chúng chậm.
Chỉ cần sử dụng pg_stat_statements?
pg_stat_statements là một phần mở rộng phổ biến được bao gồm trong bản phân phối PostgreSQL cốt lõi và có sẵn theo mặc định trên gần như tất cả các nhà cung cấp DBaaS. Nó vô giá và ít nhiều là cách duy nhất để nhận thống kê về các truy vấn mà không cần cài đặt tiện ích mở rộng tùy chỉnh.
Tuy nhiên, nó có một vài hạn chế khi phát hiện ra các truy vấn chậm.
Thống kê Tích lũy
Tiện ích mở rộng pg_stat_statements cung cấp tích lũy thống kê về mọi truy vấn từng được thực thi bởi máy chủ. Đối với mỗi truy vấn, nó hiển thị, trong số các số liệu khác, tổng số lần nó đã được thực thi và tổng thời gian thực hiện trên tất cả các lần thực thi.
Để "bắt" các truy vấn chậm khi chúng xảy ra, bạn cần định kỳ tìm nạp toàn bộ nội dung của pg_stat_statements
xem, lưu trữ nó trong cơ sở dữ liệu thời gian và so sánh số lượng thực thi. Ví dụ:nếu có nội dung của pg_stat_statements lúc 10 giờ sáng và 10 giờ 10 sáng, bạn có thể chọn những truy vấn có số lượng thực thi lúc 10 giờ 10 cao hơn lúc 10 giờ sáng. Đối với những truy vấn này, bạn có thể tính thời gian thực hiện trung bình trong khoảng thời gian này, bằng cách sử dụng:
(total time at 10.10 AM - total time at 10.00 AM) ÷ (total count at 10.10 AM - total count at 10.00 AM)
Nếu thời gian thực hiện trung bình này vượt quá ngưỡng trên, bạn có thể kích hoạt cảnh báo để thực hiện hành động.
Điều này hoạt động hợp lý trong thực tế, nhưng bạn sẽ cần một cơ sở hạ tầng giám sát tốt hoặc một dịch vụ chuyên dụng như pgDash.
Tham số Truy vấn
pg_stat_statements không nắm bắt các giá trị của các tham số ràng buộc được chuyển đến các truy vấn.
Một trong những điều mà công cụ lập kế hoạch truy vấn Postgres ước tính để chọn kế hoạch thực thi là số hàng mà một điều kiện có thể lọc ra. Ví dụ:nếu hầu hết các hàng của bảng có giá trị của một cột được lập chỉ mục quốc gia là “Hoa Kỳ”, người lập kế hoạch có thể quyết định thực hiện quét tuần tự trong toàn bộ bảng cho đâu mệnh đề country = "US"
và có thể quyết định sử dụng quét chỉ mục cho country = "UK"
kể từ lần đầu tiên ở đâu mệnh đề được mong đợi sẽ khớp với hầu hết các hàng trong bảng.
Biết giá trị thực tế của các tham số mà truy vấn thực thi chậm có thể giúp chẩn đoán các vấn đề truy vấn chậm nhanh hơn.
Ghi nhật ký truy vấn chậm
Giải pháp thay thế đơn giản hơn là ghi lại các truy vấn chậm. Không giống như một số DBMS nhất định khác giúp cho việc này trở nên dễ dàng, PostgreSQL giới thiệu cho chúng ta một loạt các cài đặt cấu hình trông giống nhau:
-
log_statement
-
log_min_duration_statement
-
log_min_duration_sample
-
log_statement_sample_rate
-
log_parameter_max_length
-
log_parameter_max_length_on_error
-
log_duration
Những điều này được mô tả chi tiết trong tài liệu Postgres. Đây là một điểm khởi đầu hợp lý:
# next line says only log queries that take longer 5 seconds
log_min_duration_statement = 5s
log_parameter_max_length = 1024
log_parameter_max_length_on_error = 1024
Kết quả là các bản ghi như sau:
2022-04-14 06:17:11.462 UTC [369399] LOG: duration: 5.060 ms statement: select i.*, t."Name" as track, ar."Name" as artist
from "InvoiceLine" as i
join "Track" as t on i."TrackId" = t."TrackId"
join "Album" as al on al."AlbumId" = t."AlbumId"
join "Artist" as ar on ar."ArtistId" = al."ArtistId";
Nếu có quá nhiều nhật ký, bạn có thể yêu cầu Postgres chỉ ghi nhật ký (giả sử) 50% các truy vấn chạy lâu hơn 5 giây:
log_min_duration_sample = 5s
log_statement_sample_rate = 0.5 # 0..1 => 0%..100%
Tất nhiên, bạn nên đọc qua tài liệu về ý nghĩa và ngụ ý của các thông số này trước khi thêm chúng vào cấu hình Postgres của mình. Hãy cảnh báo rằng các cài đặt này rất kỳ quặc và không phức tạp.
Kế hoạch thực thi của các truy vấn chậm
Nói chung là không đủ để biết rằng một truy vấn chậm đã xảy ra, bạn cũng cần phải tìm ra tại sao nó đã chậm. Đối với điều này, trước tiên bạn sẽ kiểm tra kế hoạch thực thi của truy vấn.
auto_explain
là một phần mở rộng PostgreSQL cốt lõi khác (một lần nữa, cũng có sẵn trên hầu hết các DBaaS) có thể ghi lại các kế hoạch thực thi của các truy vấn vừa kết thúc thực thi. Nó được ghi lại ở đây.
Để bật auto_explain, bạn thường phải thêm nó vào shared_preload_libraries
và khởi động lại Postgres. Đây là cấu hình khởi động mẫu:
# logs execution plans of queries that take 10s or more to run
auto_explain.log_min_duration = 10s
auto_explain.log_verbose = on
auto_explain.log_settings = on
auto_explain.log_format = json
auto_explain.log_nested_statements = on
# enabling these provide more information, but have a performance cost
#auto_explain.log_analyze = on
#auto_explain.log_buffers = on
#auto_explain.log_wal = on
#auto_explain.log_timing = on
#auto_explain.log_triggers = on
Điều này sẽ khiến các kế hoạch được ghi lại dưới dạng định dạng JSON, sau đó có thể được hiển thị trực quan trong các công cụ như thế này.
Truy vấn vẫn đang thực thi
Tất cả các kỹ thuật được liệt kê ở trên đều có một điểm chung:chúng chỉ tạo ra đầu ra có thể hành động được sau một truy vấn đã kết thúc thực thi. Chúng không thể được sử dụng để xử lý các truy vấn chậm đến mức chúng chưa thực hiện xong.
Mỗi kết nối đến máy chủ PostgreSQL được xử lý bởi một backend , cụ thể là chương trình phụ trợ khách hàng . Khi một chương trình phụ trợ như vậy đang thực thi một truy vấn, trạng thái của nó là hoạt động . Nó cũng có thể đã bắt đầu một giao dịch nhưng sau đó không hoạt động, được gọi là không hoạt động trong giao dịch trạng thái.
pg_stat_activity
chế độ xem hệ thống được ghi lại ở đây cung cấp danh sách tất cả các phần phụ trợ Postgres đang chạy. Bạn có thể truy vấn chế độ xem này để nhận các truy vấn vẫn đang chạy:
SELECT client_addr, query_start, query
FROM pg_stat_activity
WHERE state IN ('active', 'idle in transaction')
AND backend_type = 'client backend';
Nhân tiện, nếu không sử dụng tiện ích mở rộng của bên thứ ba, không có cách nào để biết kế hoạch thực thi của một truy vấn hiện đang được thực thi bởi một phần mềm phụ trợ.
Khóa
Nếu kế hoạch thực thi của một truy vấn chậm không chỉ ra bất kỳ vấn đề rõ ràng nào, thì phần phụ trợ thực thi truy vấn có thể đã bị trì hoãn bởi các khóa cạnh tranh.
Các khóa có được một cách rõ ràng hoặc ngầm định trong quá trình thực thi truy vấn vì nhiều lý do. Có cả một chương trong tài liệu Postgres dành cho điều này.
Khóa ghi nhật ký
Thông thường, giới hạn trên về thời gian chờ được đặt bằng tùy chọn lock_timeout , thường ở phía khách hàng. Nếu một truy vấn đã đợi quá lâu để có được khóa, Postgres sẽ hủy việc thực thi truy vấn này và ghi lại lỗi:
2021-01-30 09:35:52.415 UTC [67] psql postgres testdb 172.17.0.1 ERROR: canceling statement due to lock timeout
2021-01-30 09:35:52.415 UTC [67] psql postgres testdb 172.17.0.1 STATEMENT: cluster t;
Giả sử bạn muốn đặt thời gian chờ khóa là 1 phút, nhưng các truy vấn ghi nhật ký chờ khóa lâu hơn, chẳng hạn như 30 giây. Bạn có thể thực hiện việc này bằng cách sử dụng:
log_lock_waits = on
deadlock_timeout = 30s
Thao tác này sẽ tạo các nhật ký như sau:
2021-01-30 09:49:22.331 UTC [70] psql postgres testdb 172.17.0.1 LOG: process 70 still waiting for ShareLock on transaction 493 after 30009.004 ms
2021-01-30 09:49:22.331 UTC [70] psql postgres testdb 172.17.0.1 DETAIL: Process holding the lock: 68. Wait queue: 70.
2021-01-30 09:49:22.331 UTC [70] psql postgres testdb 172.17.0.1 CONTEXT: while locking tuple (0,3) in relation "t"
2021-01-30 09:49:22.331 UTC [70] psql postgres testdb 172.17.0.1 STATEMENT: select * from t for update;
Việc sử dụng deadlock_timeout không phải là lỗi đánh máy:nó là giá trị mà cơ chế ghi nhật ký chờ khóa sử dụng. Lý tưởng nhất, nên có một cái gì đó giống như log_min_duration_lock_wait , nhưng thật không may, đó không phải là trường hợp.
Trong trường hợp bế tắc thực sự, Postgres sẽ hủy bỏ các giao dịch bị tắc nghẽn sau deadlock_timeout thời hạn, và sẽ ghi lại các báo cáo vi phạm. Không cần cấu hình rõ ràng.
2021-01-30 09:55:37.724 UTC [68] psql postgres testdb 172.17.0.1 LOG: process 68 detected deadlock while waiting for ShareLock on transaction 496 after 30007.633 ms
2021-01-30 09:55:37.724 UTC [68] psql postgres testdb 172.17.0.1 DETAIL: Process holding the lock: 70. Wait queue: .
2021-01-30 09:55:37.724 UTC [68] psql postgres testdb 172.17.0.1 CONTEXT: while locking tuple (0,3) in relation "t"
2021-01-30 09:55:37.724 UTC [68] psql postgres testdb 172.17.0.1 STATEMENT: select * from t where a=4 for update;
2021-01-30 09:55:37.725 UTC [68] psql postgres testdb 172.17.0.1 ERROR: deadlock detected
2021-01-30 09:55:37.725 UTC [68] psql postgres testdb 172.17.0.1 DETAIL: Process 68 waits for ShareLock on transaction 496; blocked by process 70.
Process 70 waits for ShareLock on transaction 495; blocked by process 68.
Process 68: select * from t where a=4 for update;
Process 70: select * from t where a=0 for update;
2021-01-30 09:55:37.725 UTC [68] psql postgres testdb 172.17.0.1 HINT: See server log for query details.
2021-01-30 09:55:37.725 UTC [68] psql postgres testdb 172.17.0.1 CONTEXT: while locking tuple (0,3) in relation "t"
2021-01-30 09:55:37.725 UTC [68] psql postgres testdb 172.17.0.1 STATEMENT: select * from t where a=4 for update;
Khám phá các ổ khóa hiện tại
Toàn bộ danh sách các ổ khóa được cấp hiện có sẵn từ chế độ xem hệ thống pg_locks. Tuy nhiên, việc sử dụng hàm pg_blocking_pids
thường dễ dàng hơn , cùng với pg_stat_activity
, như thế này:
SELECT state, pid, pg_blocking_pids(pid), query
FROM pg_stat_activity
WHERE backend_type='client backend';
có thể hiển thị đầu ra như thế này:
trạng thái state | pid | pg_blocking_pids | query
---------------------+--------+------------------+-------------------------------------------------
active | 378170 | {} | SELECT state, pid, pg_blocking_pids(pid), query+
| | | FROM pg_stat_activity +
| | | WHERE backend_type='client backend';
active | 369399 | {378068} | cluster "Track";
idle in transaction | 378068 | {} | select * from "Track" for update;
(3 rows)
chỉ ra rằng có một chương trình phụ trợ bị chặn (chương trình thực thi câu lệnh CLUSTER) và nó đang bị chặn bởi PID 378068 (đã thực hiện một lệnh SELECT..FOR UPDATE nhưng sau đó đang chạy không tải trong giao dịch).