hashub

hashub

【PgSQL】定位函數中最耗時的SQL

一、問題背景#

對於 pg 中某函數執行時間超過 30 分鐘,查看慢在其中哪一段 SQL。但是如果直接通過 pg_stat_activity 查詢,只能看到上層執行函數的語句,而不像 oracle 和 SqlServer 能看到當前在執行什麼。

create table test(id int);
INSERT INTO test(id) VALUES (generate_series(1, 10000));
 
create table test2(id int);
INSERT INTO test2(id) VALUES (generate_series(1, 10000));
create or replace function test_f() returns bigint
as $$
	select count(*) from test;
	select count(*) from test2;
	select count(*) from test,test2;
$$ language sql;

執行函數

test_db=# \timing
Timing is on.
test_db=# select test_f();
  test_f
-----------
 100000000
(1 row)
Time: 16203.883 ms

查看運行情況

-- PG >= 9.6
select pid,usename,substring(query from 0 for 50),now()-query_start as time,wait_event_type,wait_event from pg_stat_activity where state = 'active';


  pid   | usename  |                     substring                     |      time       | wait_event_type | wait_event
--------+----------+---------------------------------------------------+-----------------+-----------------+------------
 395096 | postgres | select test_f();                                  | 00:00:01.197204 |                 |

二、方法彙總及對比#

方法直接分析函數代碼raise notice 打標記auto_explain 設置 pg_nested_statements=truepg_stat_statements.trask=all
優點直截了當,適合函數代碼非常簡單的場景。1. 直截了當
2. 可以在程序運行時定位慢 SQL
1. 可以顯示函數中每個 SQL 的執行計劃和執行時間。
2. 可以在全局或會話級開啟
1. 可以顯示函數中每個 sQL 的統計數據,間接分析慢 SQL
2. 可以在全局或會話級開啟。
缺點對於複雜函數,基本不可行。對於複雜函數,需要打的標記非常多,不太方便1. 對於高負載、函數多的庫,基本不能在全局開啟
2. 會話級開啟需要手動執行,不能在程序運行時分析
3. 對於有 dml 操作的函數,不建議執行或者需開啟事務執行後回滾。
1. 對於高負載、函數多的庫,基本不能在全局開啟
2. 對於 SQL 執行多的庫,比 auto_explain 更難定位
3. 會話級開啟需要手動執行,不能在程序運行時分析
4. 對於有 dml 操作的函數,不建議執行或者需開啟事務執行後回滾,

附上之前記錄的 Oracle 和 SQL Server 的定位方法:

Oracle 存儲過程定位慢 SQL 方法_oracle 慢 sql_Hehuyi_In 的博客 - CSDN 博客

SQL Server 如何找到存儲過程中最耗時的部分_Hehuyi_In 的博客 - CSDN 博客

直接分析函數代碼#

\sf 函數名 可以查看函數代碼,這適用於函數非常簡單的場景

\sf test_f

如果函數中 SQL 很長,輸出可能會錯行,不方便分析,可以用 psql 將其導出成文本。

psql -c "\sf test_f" > test_f.sql

raise notice 打標記#

注意 raise 要用 plpgsql 語言,像上面用 sql 會報錯,為方便測試稍微做點改造。

CREATE OR REPLACE FUNCTION test_f_2() RETURNS integer AS $$
declare num int;
BEGIN
	raise notice 'notice 1, start at: %',clock_timestamp();
	insert into test select count(*) from test;
	raise notice 'notice 2, start at: %',clock_timestamp();
	select count(*) into num from test,test2;
	raise notice 'notice 3, start at: %',clock_timestamp();
	insert into test2 select count(*) from test2;
	raise notice 'notice end, end at: %',clock_timestamp();
	return num;
END;
$$ LANGUAGE plpgsql;
select test_f_2();

auto_explain 顯示每個 SQL 執行計劃#

對於業務量大的庫,不適合全局抓取 SQL,可以在客戶端開啟
設置以下參數,核心是 log_nested_statements = true,展示函數內所有 SQL 執行計劃。

擴展 auto_explain 的安裝,詳見:[[6. Extensions-PG#auto_explain]]

LOAD 'auto_explain';
set client_min_messages='log';
set auto_explain.log_min_duration = 0;
set auto_explain.log_analyze = true;
set auto_explain.log_verbose = true;
set auto_explain.log_buffers = true;
set auto_explain.log_nested_statements = true;
test_db=# select test_f();
-- SQL 1 執行時間
LOG:  duration: 0.984 ms  plan:
Query Text:
select count(*) from test;
select count(*) from test2;
select count(*) from test,test2;

Aggregate  (cost=170.00..170.01 rows=1 width=8) (actual time=0.983..0.983 rows=1 loops=1)
  Output: count(*)
  Buffers: shared hit=45
  ->  Seq Scan on public.test  (cost=0.00..145.00 rows=10000 width=0) (actual time=0.003..0.568 rows=10002 loops=1)
        Output: id
        Buffers: shared hit=45

-- SQL 2 執行時間
LOG:  duration: 1.114 ms  plan:
Query Text:
select count(*) from test;
select count(*) from test2;
select count(*) from test,test2;

...

-- 總的執行時間
LOG:  duration: 11760.851 ms  plan:
Query Text: select test_f();
Result  (cost=0.00..0.26 rows=1 width=4) (actual time=11760.840..11760.841 rows=1 loops=1)
  Output: test_f()
  Buffers: shared hit=232 dirtied=1
  test_f
-----------
 100040004
(1 row)

設置 pg_stat_statements.track=all#

基本上也只適合客戶端開啟,或者在測試環境 reset 後單獨跑,比 auto_explain 更難找
postgresql.conf

shared_preload_libraries = 'pg_stat_statements'

compute_query_id = on
pg_stat_statements.max = 10000
pg_stat_statements.track = all
set pg_stat_statements.track="all";
select test_f();

select userid::regrole,total_exec_time,query from pg_stat_statements order by total_exec_time desc;

搬運自:
PostgreSQL 定位函數中最耗時的 SQL

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。