Khi làm việc với cơ sở dữ liệu OLTP (Xử lý giao dịch trực tuyến), hiệu suất truy vấn là điều tối quan trọng vì nó tác động trực tiếp đến trải nghiệm người dùng. Các truy vấn chậm có nghĩa là ứng dụng không phản hồi và chậm và điều này dẫn đến tỷ lệ chuyển đổi kém, người dùng không hài lòng và tất cả các vấn đề.
OLTP là một trong những trường hợp sử dụng phổ biến cho PostgreSQL, do đó bạn muốn các truy vấn của mình chạy trơn tru nhất có thể. Trong blog này, chúng tôi muốn nói về cách bạn có thể xác định các vấn đề với các truy vấn chậm trong PostgreSQL.
Hiểu Nhật ký Chậm
Nói chung, cách điển hình nhất để xác định các vấn đề về hiệu suất với PostgreSQL là thu thập các truy vấn chậm. Có một số cách bạn có thể làm điều đó. Trước tiên, bạn có thể kích hoạt nó trên một cơ sở dữ liệu duy nhất:
pgbench=# ALTER DATABASE pgbench SET log_min_duration_statement=0;
ALTER DATABASE
Sau đó, tất cả các kết nối mới đến cơ sở dữ liệu ‘pgbench’ sẽ được đăng nhập vào nhật ký PostgreSQL.
Cũng có thể bật tính năng này trên toàn cầu bằng cách thêm:
log_min_duration_statement = 0
đến cấu hình PostgreSQL rồi tải lại cấu hình:
pgbench=# SELECT pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)
Điều này cho phép ghi lại tất cả các truy vấn trên tất cả các cơ sở dữ liệu trong PostgreSQL của bạn. Nếu bạn không thấy bất kỳ nhật ký nào, bạn cũng có thể muốn bật logging_collector =. Các bản ghi sẽ bao gồm tất cả lưu lượng truy cập đến các bảng hệ thống PostgreSQL, làm cho nó trở nên ồn ào hơn. Vì mục đích của chúng tôi, chúng ta hãy tuân theo việc ghi nhật ký cấp cơ sở dữ liệu.
Những gì bạn sẽ thấy trong nhật ký là các mục nhập như sau:
2020-02-21 09:45:39.022 UTC [13542] LOG: duration: 0.145 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 29817899;
2020-02-21 09:45:39.022 UTC [13544] LOG: duration: 0.107 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 11782597;
2020-02-21 09:45:39.022 UTC [13529] LOG: duration: 0.065 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 16318529;
2020-02-21 09:45:39.022 UTC [13529] LOG: duration: 0.082 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + 3063 WHERE tid = 3244;
2020-02-21 09:45:39.022 UTC [13526] LOG: duration: 16.450 ms statement: UPDATE pgbench_branches SET bbalance = bbalance + 1359 WHERE bid = 195;
2020-02-21 09:45:39.023 UTC [13523] LOG: duration: 15.824 ms statement: UPDATE pgbench_accounts SET abalance = abalance + -3726 WHERE aid = 5290358;
2020-02-21 09:45:39.023 UTC [13542] LOG: duration: 0.107 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + -2716 WHERE tid = 1794;
2020-02-21 09:45:39.024 UTC [13544] LOG: duration: 0.112 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + -3814 WHERE tid = 278;
2020-02-21 09:45:39.024 UTC [13526] LOG: duration: 0.060 ms statement: INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (4876, 195, 39955137, 1359, CURRENT_TIMESTAMP);
2020-02-21 09:45:39.024 UTC [13529] LOG: duration: 0.081 ms statement: UPDATE pgbench_branches SET bbalance = bbalance + 3063 WHERE bid = 369;
2020-02-21 09:45:39.024 UTC [13523] LOG: duration: 0.063 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 5290358;
2020-02-21 09:45:39.024 UTC [13542] LOG: duration: 0.100 ms statement: UPDATE pgbench_branches SET bbalance = bbalance + -2716 WHERE bid = 210;
2020-02-21 09:45:39.026 UTC [13523] LOG: duration: 0.092 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + -3726 WHERE tid = 67;
2020-02-21 09:45:39.026 UTC [13529] LOG: duration: 0.090 ms statement: INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (3244, 369, 16318529, 3063, CURRENT_TIMESTAMP);
Bạn có thể xem thông tin về truy vấn và thời lượng của nó. Không có gì khác nhưng đó chắc chắn là một nơi tốt để bắt đầu. Điều chính cần ghi nhớ là không phải mọi truy vấn chậm đều là một vấn đề. Đôi khi các truy vấn phải truy cập một lượng dữ liệu đáng kể và chúng phải mất nhiều thời gian hơn để truy cập và phân tích tất cả thông tin mà người dùng yêu cầu. Một câu hỏi khác là "chậm" nghĩa là gì? Điều này chủ yếu phụ thuộc vào ứng dụng. Nếu chúng ta đang nói về các ứng dụng tương tác, rất có thể bất kỳ thứ gì chậm hơn một giây là đáng chú ý. Lý tưởng nhất là mọi thứ được thực thi trong giới hạn 100 - 200 mili giây.
Phát triển Kế hoạch Thực thi Truy vấn
Khi chúng tôi xác định rằng truy vấn đã cho thực sự là thứ mà chúng tôi muốn cải thiện, chúng tôi nên xem xét kế hoạch thực thi truy vấn. Trước hết, có thể xảy ra trường hợp chúng tôi không thể làm gì được và chúng tôi sẽ phải chấp nhận rằng truy vấn nhất định chỉ là chậm. Thứ hai, kế hoạch thực hiện truy vấn có thể thay đổi. Trình tối ưu hóa luôn cố gắng chọn kế hoạch thực thi tối ưu nhất nhưng họ đưa ra quyết định chỉ dựa trên một mẫu dữ liệu, do đó có thể xảy ra trường hợp kế hoạch thực hiện truy vấn thay đổi theo thời gian. Trong PostgreSQL, bạn có thể kiểm tra kế hoạch thực thi theo hai cách. Đầu tiên, kế hoạch thực hiện ước tính, sử dụng EXPLAIN:
pgbench=# EXPLAIN SELECT abalance FROM pgbench_accounts WHERE aid = 5290358;
QUERY PLAN
----------------------------------------------------------------------------------------------
Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.56..8.58 rows=1 width=4)
Index Cond: (aid = 5290358)
Như bạn thấy, chúng tôi dự kiến sẽ truy cập dữ liệu bằng cách sử dụng tra cứu khóa chính. Nếu chúng ta muốn kiểm tra lại cách thực thi chính xác truy vấn, chúng ta có thể sử dụng PHÂN TÍCH GIẢI THÍCH:
pgbench=# EXPLAIN ANALYZE SELECT abalance FROM pgbench_accounts WHERE aid = 5290358;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.56..8.58 rows=1 width=4) (actual time=0.046..0.065 rows=1 loops=1)
Index Cond: (aid = 5290358)
Planning time: 0.053 ms
Execution time: 0.084 ms
(4 rows)
Bây giờ, PostgreSQL đã thực thi truy vấn này và nó có thể cho chúng ta biết không chỉ ước tính mà còn cho chúng ta biết những con số chính xác khi nói đến kế hoạch thực thi, số hàng được truy cập, v.v. Xin lưu ý rằng việc ghi nhật ký tất cả các truy vấn có thể trở thành một chi phí nghiêm trọng trên hệ thống của bạn. Bạn cũng nên theo dõi các nhật ký và đảm bảo chúng được xoay đúng cách.
Pg_stat_statements
Pg_stat_statements là tiện ích mở rộng thu thập thống kê thực thi cho các loại truy vấn khác nhau.
pgbench=# select query, calls, total_time, min_time, max_time, mean_time, stddev_time, rows from public.pg_stat_statements order by calls desc LIMIT 10;
query | calls | total_time | min_time | max_time | mean_time | stddev_time | rows
------------------------------------------------------------------------------------------------------+-------+------------------+----------+------------+---------------------+---------------------+-------
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2 | 30437 | 6636.83641200002 | 0.006533 | 83.832148 | 0.218051595492329 | 1.84977058799388 | 30437
BEGIN | 30437 | 231.095600000001 | 0.000205 | 20.260355 | 0.00759258796859083 | 0.26671126085716 | 0
END | 30437 | 229.483213999999 | 0.000211 | 16.980678 | 0.0075396134310215 | 0.223837608828596 | 0
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2 | 30437 | 290021.784321001 | 0.019568 | 805.171845 | 9.52859297305914 | 13.6632712046825 | 30437
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2 | 30437 | 6667.27243200002 | 0.00732 | 212.479269 | 0.219051563294674 | 2.13585110968012 | 30437
SELECT abalance FROM pgbench_accounts WHERE aid = $1 | 30437 | 3702.19730600006 | 0.00627 | 38.860846 | 0.121634763807208 | 1.07735927551245 | 30437
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP) | 30437 | 2349.22475800002 | 0.003218 | 61.372127 | 0.0771831901304325 | 0.971590327400244 | 30437
SELECT $1 | 6847 | 60.785467 | 0.002321 | 7.882384 | 0.00887767883744706 | 0.105198744982906 | 6847
insert into pgbench_tellers(tid,bid,tbalance) values ($1,$2,$3) | 5000 | 18.592042 | 0.001572 | 0.741427 | 0.0037184084 | 0.0137660355678027 | 5000
insert into pgbench_tellers(tid,bid,tbalance) values ($1,$2,$3) | 3000 | 7.323788 | 0.001598 | 0.40152 | 0.00244126266666667 | 0.00834442591085048 | 3000
(10 rows)
Như bạn có thể thấy trên dữ liệu ở trên, chúng tôi có danh sách các truy vấn khác nhau và thông tin về thời gian thực hiện của chúng - đây chỉ là một phần dữ liệu bạn có thể thấy trong pg_stat_statements nhưng nó đủ cho chúng tôi hiểu rằng việc tra cứu khóa chính của chúng tôi đôi khi mất gần 39 giây để hoàn thành - điều này có vẻ không ổn và đó chắc chắn là điều chúng tôi muốn điều tra.
Nếu bạn chưa bật pg_stat_statements, bạn có thể thực hiện theo cách chuẩn. Qua tệp cấu hình và
shared_preload_libraries = 'pg_stat_statements'
Hoặc bạn có thể bật nó qua dòng lệnh PostgreSQL:
pgbench=# CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION
Sử dụng ClusterControl để loại bỏ các truy vấn chậm
Nếu bạn tình cờ sử dụng ClusterControl để quản lý cơ sở dữ liệu PostgreSQL của mình, bạn có thể sử dụng nó để thu thập dữ liệu về các truy vấn chậm.
Như bạn có thể thấy, nó thu thập dữ liệu về việc thực thi truy vấn - các hàng được gửi và đã kiểm tra, thống kê thời gian thực hiện, v.v. Với nó, bạn có thể dễ dàng xác định các truy vấn đắt nhất và xem thời gian thực hiện trung bình và tối đa như thế nào. Theo mặc định, ClusterControl thu thập các truy vấn mất hơn 0,5 giây để hoàn thành, bạn có thể thay đổi điều này trong cài đặt:
Kết luận
Blog ngắn này hoàn toàn không bao gồm tất cả các khía cạnh và công cụ hữu ích trong việc xác định và giải quyết các vấn đề về hiệu suất truy vấn trong PostgreSQL. Chúng tôi hy vọng đó là một khởi đầu tốt và nó sẽ giúp bạn hiểu những gì bạn có thể làm để xác định nguyên nhân gốc rễ của các truy vấn chậm.