postgres fdw是一種外部訪問接口,它可以被用來訪問存儲在外部的數據,這些數據可以是外部的pg數據庫,也可以oracle、mysql等數據庫,甚至可以是文件。 
 而本章節主要介紹 postgres_fdw, postgres_fdw 專門為同構數據庫實例提供的外部封裝訪問擴展應用。 
 該模塊提供的功能與舊dblink模塊的功能基本重疊。但是postgres_fdw 
 為訪問遠程表提供了更透明和符合標准的語法,並且在許多情況下可以提供更好的性能。而我們的PostgreSQL 內置sharding 也將是基於postgres_fdw實現。 
 使用postgres_fdw產要有以下步驟:
- 創建擴展
 - 創建服務
 - 創建用戶映射
 - 創建與訪問表對應的外表
 
到此就可以使用SELECT從外部表中訪問存儲在其底層遠程表中的數據。同時可以 UPDATE,INSERT,DELETE遠程表數據庫,前提是在用戶映射中指定的遠程用戶必須具有執行這些操作的權限。 
 
更多支持可以參考: https://wiki.postgresql.org/wiki/Foreign_data_wrappers
1. 安裝使用
這里創建2個數據庫db01,db02,2個用戶user01,user02分別用來作為本地和遠端的數據庫和用戶。
1.1 初始化數據
#實始化user01,db01 和 user02,db02
create user user01 superuser  password 'user01';    
create database db01 owner=user01 TEMPLATE=template0 LC_CTYPE='zh_CN.UTF-8';
create user user02 superuser password 'user02';
create database db02 with owner=user02 TEMPLATE=template0 LC_CTYPE='zh_CN.UTF-8';
#在db02下創建表
\c db02 user02 
create table table1 (id int, crt_Time timestamp, info text, c1 int);
create table table2 (id int, crt_Time timestamp, info text, c1 int); 
insert into table1 select generate_series(1,1000000), clock_timestamp(), md5(random()::text), random()*1000;  
insert into table2 select generate_series(1,1000000), clock_timestamp(), md5(random()::text), random()*1000;   
        1.2 安裝fdw
1.2.1安裝
postgres@s2ahumysqlpg01-> psql
psql (12.4)
Type "help" for help.
postgres=# \c db01 user01 ;
Password for user user01: 
You are now connected to database "db01" as user "user01".
db01=# create extension postgres_fdw;
CREATE EXTENSION 
        1.2.2 查看系統表
通過下列系統表可以查看數據庫外部表信息。
| 系統表 | 簡命令操作 | 含義 |
|:-----:-----:-----:-----:----|
| pg_extension | \dx | 插件 |
| pg_foreign_data_wrapper | \dew | 支持外部數據庫接口 |
| pg_foreign_server | \des | 外部服務器 |
| pg_user_mappings | \deu | 用戶管理 |
| pg_foreign_table | \det | 外部表 |
# 示例:
postgres=# \deu
List of user mappings
 Server | User name 
--------+-----------
(0 rows)
postgres=# \det
 List of foreign tables
 Schema | Table | Server 
--------+-------+--------
(0 rows) 
        1.3 創建服務
db01=# CREATE SERVER db02  
                FOREIGN DATA WRAPPER postgres_fdw  
                OPTIONS (host '192.168.1.55', port '5432', dbname 'db02'); 
CREATE SERVER
db01=#  
db01=# select * from pg_foreign_server ;  
   oid  | srvname | srvowner | srvfdw | srvtype | srvversion | srvacl |                srvoptions                 
-------+---------+----------+--------+---------+------------+--------+-------------------------------------------
 42887 | db02    |    42860 |  42885 |         |            |        | {host=192.168.1.55,port=5432,dbname=db02}
(1 row) 
        1.4 創建用戶映射
# 創建用戶user01 與  遠端用戶user02的映射 
db01=# CREATE USER MAPPING
              FOR user01 
             server db02 
             options(user 'user02',password 'user02');
CREATE USER MAPPING
db01=# select * from pg_user_mappings ;
 umid  | srvid | srvname | umuser | usename  |           umoptions           
-------+-------+---------+--------+----------+-------------------------------
  42892 | 42887 | db02    |  42860 | user01   | {user=user02,password=user02}
(1 row)
 
        1.5 創建外鍵表
#方法一:批量導入,這種比較常見,可以一次導入一個模式下的所有表
db01=# import foreign schema public from server db02 into public;
IMPORT FOREIGN SCHEMA
db01=#  \det
    List of foreign tables
 Schema |   Table    | Server 
--------+------------+-------
 public | table1     | db02
 public | table2     | db02
(2 rows)
# 或者指定表導入
 IMPORT FOREIGN SCHEMA  public  limit to (table1,table2) from server db02 into public;
#方法二:創建單個鍵表 
#先刪除外鍵表
db01=# DROP FOREIGN TABLE  table1,table2 ;
DROP FOREIGN TABLE
#單個表映射
db01=#  CREATE FOREIGN TABLE  table1 ( id int, crt_Time timestamp, info text, c1 int  )   
                                               SERVER db02  
                                               OPTIONS (schema_name 'public', table_name 'table1');  
CREATE FOREIGN TABLE
db01=# CREATE FOREIGN TABLE  table2 ( id int, crt_Time timestamp, info text, c1 int  )   
                                              SERVER db02  
                                              OPTIONS (schema_name 'public', table_name 'table2');  
CREATE FOREIGN TABLE
db01=# \d
    List of foreign tables
 Schema |   Table    | Server 
--------+------------+-------
 public | table1     | db02
 public | table2     | db02
(2 rows)
 
        2.使用示例
2.1 查詢操作
db01=# select count(*)  from table1 ;
  count  
---------
 1000000
(1 row)
db01=#  select  *  from table1 where id < 5 ;
 id |          crt_time          |               info               | c1  
----+----------------------------+----------------------------------+-----
  1 | 2022-03-07 10:50:06.823333 | bca78dace57fdc25c752140bf84231cf | 916
  2 | 2022-03-07 10:50:06.82347  | 7761db8044710ed9c753124ee2b7f2a8 |   2
  3 | 2022-03-07 10:50:06.823482 | 974f5189cc308bb87216e3a695ae7716 | 718
  4 | 2022-03-07 10:50:06.823488 | 27adfaf05acd9cf6c9eb11173f3bcbf0 | 863
(4 rows)
-- 和外部表關聯查詢。
db01-# SELECT t1.id, t2.crt_time
FROM table1 t1 
INNER JOIN table2 t2 ON t1.id = t2.id
WHERE t1.id < 10;
 id |          crt_time          
----+----------------------------
  1 | 2022-03-07 10:50:14.235354
  2 | 2022-03-07 10:50:14.235581
  3 | 2022-03-07 10:50:14.235601
  4 | 2022-03-07 10:50:14.23561
  5 | 2022-03-07 10:50:14.235618
  6 | 2022-03-07 10:50:14.235627
  7 | 2022-03-07 10:50:14.235672
  8 | 2022-03-07 10:50:14.235683
  9 | 2022-03-07 10:50:14.235692
(9 rows)
 
        2.2 寫操作
postgres_fdw 外部表一開始只支持讀,PostgreSQL9.3 版本開始支持可寫。 
 寫操作需要保證:1. 映射的用戶對有寫權限;2. 版本需要9.3 以上
# 刪除示例
db01=# select count(*)  from table1 ;
  count  
---------
 1000000
(1 row)
db01=# delete from  table1 where  id <= 10;
DELETE 10
db01=# select count(*)  from table1 ;
 count  
--------
 999990
(1 row)
# 插入
db01=# insert into table1 select generate_series(1,5), clock_timestamp(), md5(random()::text), random()*1000;  
INSERT 0 5
db01=#  select count(*)  from table1 ;
 count  
--------
 999995
(1 row)
# 更改
db01=# select * from table1 where  id =5 ;
 id |          crt_time          |               info               | c1  
----+----------------------------+----------------------------------+-----
  5 | 2022-03-07 11:28:09.735544 | 0f5d447efe103c8bc83d0980005b7177 | 187
(1 row)
db01=# update table1 set info='ahser.hu' where  id =5 ;
UPDATE 1
db01=#  select * from table1 where  id =5 ;
 id |          crt_time          |   info   | c1  
----+----------------------------+----------+-----
  5 | 2022-03-07 11:28:09.735544 | ahser.hu | 187
(1 row)
 
        3.補充
3.1支持聚合下推
PostgreSQL10 增強了postgres_fdw 擴展模塊的特性,可以將聚合、關聯操作下推到遠程PostgreSQL數據庫進行,而之前的版本是將外部表相應的遠程數據全部取到本地再做聚合,10版本這個心特性大幅度減少了從遠程傳輸到本地庫的數據量。提升了postgres_fdw外部表上聚合查詢的性能。
db01=# EXPLAIN(ANALYZE on,VERBOSE on) select id,count(*) from table1  where id < 100 group by id;
                                              QUERY PLAN                                              
------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=109.75..198.89 rows=200 width=12) (actual time=148.884..148.969 rows=94 loops=1)
   Output: id, (count(*))
   Relations: Aggregate on (public.table1)
   Remote SQL: SELECT id, count(*) FROM public.table1 WHERE ((id < 100)) GROUP BY 1
 Planning Time: 0.136 ms
 Execution Time: 149.688 ms
(6 rows)
#其中 remote sql: 表示遠程庫上執行的SQL,此SQL為聚合查詢的SQL。聚合是在遠程上執行的。
 
        3.2 FDW在PG14的新功能
1.批入導入性能提升 
 INSERT SELECT 語句將 100 萬行從另一個表插入到該表所用的時間如下。postgres_fdw OPTIONS的 batch_size 參數設置為 100。這意味着一次最多向外部服務器發送 100 行:
- 沒有 FDW 的本地表:6.1 秒
 - 帶 FDW 的遠程表(改進前):125.3 秒
 - 帶 FDW 的遠程表(改進后):11.1 秒
 
2.FDW 外部表接口支持 truncate [only|cascade] ,可能通過truncatable 參數選項控制默認為true 
 3.遠程更新參數控制 ,默認情況下,所有使用的外部表postgres_fdw 
 都假定是可更新的 。可能通過updatable參數選項控制默認為true 
 4.支持並行/異步 外部掃描,充許一個查詢引用多個外部表,並行執行外部表掃描。選項async_capable,它允許並行計划和執行外部表掃描。 
 5.LIMIT TO 子分區,如果指定IMPORT FOREIGN SCHEMA … LIMIT TO,則允許postgres_fdw導入表分區。默認情況下postgres_fdw不允許導入表分區,因為可以使用根分區訪問數據。如果用戶想要導入分區表分區,PostgreSQL 14添加了一個新的選項LIMIT TO指定子分區導入。 
 6.保持連接,添加了一個新選項keep_connections,以保持連接處於活動狀態,以便后續查詢可以重用它們。默認情況下,此選項處於on狀態,但如果off,則在事務結束時將丟棄連接。
- 如果在關閉這個選項,可以使用 ALTER SERVER youserrvername OPTIONS (keep_connections 'off');
 - 打開使用ALTER SERVER youserrvername options (set keep_connections 'on');
 
7.活動和有效的連接列表,添加postgres_fdw_get_connections函數以報告打開的外部服務連接。該函數將打開的連接名本地會話返回到postgres_fdw的外部服務。它還輸出連接的有效性。
- 查詢從本地會話到外部服務器建立的所有打開連接的外部服務器名稱: SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
 - 丟棄從本地會話到外部服務器建立的所有打開連接: select postgres_fdw_disconnect_all();
 
參考:
支持的外部接口:https://wiki.postgresql.org/wiki/Foreign_data_wrappers 
 FDW例表:https://pgxn.org/tag/fdw/ 
 PostgreSQL分片實現: https://wiki.postgresql.org/wiki/WIP_PostgreSQL_Sharding 
 官方文檔:https://www.postgresql.org/docs/current/postgres-fdw.html 
 http://postgres-road.blogspot.com/2021/03/faster-bulk-insertion-to-foreign-tables.html 
 https://www.percona.com/blog/2021/05/27/new-features-in-postgresql-14-bulk-inserts-for-foreign-data-wrappers/

