天天看點

在資料庫中跑背景長任務

标簽

PostgreSQL , dblink , 長任務

https://github.com/digoal/blog/blob/master/201806/20180621_03.md#%E8%83%8C%E6%99%AF 背景

如果業務上需要在資料庫中跑LONG SQL,并且不希望跑的過程中因為視窗斷開,導緻資料庫任務使用者主動cancel query。有什麼方法?

使用DBLINK異步調用是不錯的方法,相當于資料庫内部建立了連接配接在背景跑。

https://github.com/digoal/blog/blob/master/201806/20180621_03.md#%E6%96%B9%E6%B3%95 方法

1、建立任務表,友善觀察任務狀态

create table tbl_task (  
  id serial8 primary key,  -- 任務ID  
  client_info jsonb,       -- 用戶端描述(usename, datname, search_path, client_addr, client_port)  
  sql text,                -- SQL資訊  
  start_time timestamp,    -- 開始時間  
  end_time timestamp default clock_timestamp(),  -- 結束時間  
  info text                -- 描述資訊  
);  
           

2、建立dblink 插件

create extension dblink;  
           

3、建立生成dblink連接配接的函數,重複建立不報錯。

create or replace function conn(    
  name,   -- dblink名字    
  text    -- 連接配接串,URL    
) returns void as $$      
declare      
begin      
  perform dblink_connect($1, $2);     
  return;      
exception when others then      
  return;      
end;      
$$ language plpgsql strict;    
           

4、建立異步調用封裝函數

create or replace function run_task(    
  sql text,       -- 要執行的SQL  
  info text,             -- 任務描述  
  conn_name name  default 'link_for_task', -- dblink 名字  
  conn text  default format('hostaddr=%s port=%s user=%s dbname=%s application_name=run_task', '127.0.0.1', current_setting('port'), current_user, current_database())  ,    -- 連接配接串   
  client_info jsonb   default format('{"client_addr":"%s", "client_pot":"%s", "search_path":"%s", "usename":"%s", "datname":"%s"}', inet_client_addr(), inet_client_port(), replace(current_setting('search_path'),'"','\"'), current_user, current_database())::jsonb   -- 用戶端資訊  
) returns void as $$     
declare    
begin    
  perform conn(conn_name,  conn);             -- 連接配接。       
    -- perform dblink_get_result(conn_name);    -- 消耗掉上一次異步連接配接的結果,否則會報錯。      
      
  -- 發送異步DBLINK調用    
  perform dblink_send_query(conn_name, sql||format('; insert into tbl_task(client_info,sql,start_time,info) values (%L, %L, %L, %L);', client_info, sql, clock_timestamp(),info ));      
end;    
$$ language plpgsql strict;    
           

5、調用異步調用封裝函數

select run_task(  
  'select count(*) from test',   -- 要RUN的SQL  
  'test dblink async call'       -- 任務描述  
);  
           

6、檢視目前正在跑的背景任務

postgres=# select * from pg_stat_activity where application_name='run_task';  
-[ RECORD 1 ]----+---------------------------------------------------------------------------------------------  
datid            | 13285  
datname          | postgres  
pid              | 1510  
usesysid         | 10  
usename          | postgres  
application_name | run_task  
client_addr      | 127.0.0.1  
client_hostname  |   
client_port      | 55088  
backend_start    | 2018-06-21 18:04:20.964586+08  
xact_start       |   
query_start      | 2018-06-21 18:04:20.967177+08  
state_change     | 2018-06-21 18:04:20.969363+08  
wait_event_type  | Client  
wait_event       | ClientRead  
state            | idle  
backend_xid      |   
backend_xmin     |   
query            | select count(*) from test; insert into tbl_task(client_info,sql,start_time,info) values (E'{"datname": "postgres", "usename": "postgres", "client_pot": "", "client_addr": "", "search_path": "\\"$user\\", public"}', 'select count(*) from test', '2018-06-21 18:04:20.967118+08', 'test dblink async call')  
backend_type     | client backend  
           

7、檢視任務狀态

postgres=# select * from tbl_task;  
-[ RECORD 1 ]------------------------------------------------------------------------------------------------------------------------  
id          | 1  
client_info | {"datname": "postgres", "usename": "postgres", "client_pot": "", "client_addr": "", "search_path": "\"$user\", public"}  
sql         | select count(*) from test  
start_time  | 2018-06-21 18:07:39.60439  
end_time    | 2018-06-21 18:07:47.331041  
info        | test dblink async call  
           

https://github.com/digoal/blog/blob/master/201806/20180621_03.md#%E5%A6%82%E6%9E%9C%E6%98%AF%E6%95%B0%E6%8D%AE%E5%BA%93%E6%99%AE%E9%80%9A%E7%94%A8%E6%88%B7%E8%B0%83%E7%94%A8%E4%B8%8D%E6%94%AF%E6%8C%81trust%E8%AE%A4%E8%AF%81 如果是資料庫普通使用者調用,不支援trust認證

如果是普通使用者,請使用密碼認證,同時請務必保障pg_hba.conf使用的是密碼認證。

比如阿裡雲RDS PPAS的使用者:

select run_task(  
  'select count(*) from test',   -- 要RUN的SQL  
  'test dblink async call' ,      -- 任務描述  
  'link_task', 
  format('hostaddr=%s port=%s user=%s dbname=%s application_name=run_task, password=%s', '127.0.0.1', current_setting('port'), current_user, current_database(), '目前使用者密碼')  
);  
           

如果是阿裡雲RDS PG 9.4的使用者,核心做過修改,請使用如下方法

select run_task(  
  'select count(*) from test',   -- 要RUN的SQL  
  'test dblink async call' ,      -- 任務描述  
  'link_task', 
  format('user=%s dbname=%s application_name=run_task, password=%s', current_user, current_database(), '密碼')  
);  
           

https://github.com/digoal/blog/blob/master/201806/20180621_03.md#%E6%B3%A8%E6%84%8F 注意

1、DBLINK異步連接配接會占用連接配接,算連接配接數的。

2、單個DBLINK連接配接,如果異步調用的SQL沒有執行完,不能發起第二次請求。

NOTICE:  could not send query: another command is already in progress
           

那麼你需要看看這個DBLINK的背景任務是否還在執行(通過前面查詢pg_stat_activity的方法, 執行完state狀态為idle),如果執行完了,那麼執行以下SQL取一下結果,就可以繼續使用這個DBLINK NAME發送異步請求了。

select * from dblink_get_result('link_for_task') as t(id text);
           

或者,你可以不用等這個DBLINK的異步任務執行完,馬上想發起另一個異步任務,那麼你需要使用一個新的DBLINK NAME。

select run_task(
  $$select count(*) from test where c1='abc'$$,   -- 要RUN的SQL
  'test dblink async call',       -- 任務描述
  'new_dblink_name'   -- 有别于前面已使用的DBLINK NAME
);
           

3、單個DBLINK連接配接,如果異步調用的SQL執行完了,調用dblink_get_result後,才能發起第二次請求。

NOTICE:  could not send query: another command is already in progress
           

4、單個DBLINK連接配接,如果異步調用的SQL沒有執行完,調用dblink_get_result時,會等待異步調用執行完,才會有傳回。等待過程中堵塞目前調用dblink_get_result的會話。

5、調用DBLINK異步接口的會話如果斷開了,那麼它發起的dblink異步調用背景任務執行完成後,連接配接會自動釋放。

6、如果SQL中包含單引号,可以使用轉義的寫法,也可以使用沒有符号的寫法,不需要轉義。

select run_task(
  $$select count(*) from test where c1='abc'$$,   -- 要RUN的SQL
  'test dblink async call'       -- 任務描述
);
           

select run_task(
  'select count(*) from test where c1=''abc''',   -- 要RUN的SQL
  'test dblink async call'       -- 任務描述
);
           
select run_task(
  E'select count(*) from test where c1=\'abc\'',   -- 要RUN的SQL
  'test dblink async call'       -- 任務描述
);
           

美元符号内可以輸入任意個字元,成對出現即可。

select run_task(
  $_qqq_$select count(*) from test where c1='abc'$_qqq_$,   -- 要RUN的SQL
  'test dblink async call'       -- 任務描述
);
           

https://github.com/digoal/blog/blob/master/201806/20180621_03.md#%E5%8F%82%E8%80%83 參考

https://www.postgresql.org/docs/10/static/dblink.html 《PostgreSQL 批量導入性能 (采用dblink 異步調用)》