はじめに
PostgreSQLでDBリンクを実現するには2つのやり方があります。何れも拡張モジュールを導入する事になるのですが今回は postgres_fdw ( contribモジュールに含まれる )を利用します。従来から存在する「dblinkモジュール」を使う方法より、パフォーマンス的にもこちらがお勧めです。(PostgreSQL 9.3以降に追加された機能のようです)何より従来のdblinkモジュールと大きく異なるのはリモート側テーブルへのアクセスとローカル側テーブルへのアクセスを同一トランザクションで行える事です。
今回はCentOS Linux8.5+PostgreSQL 13.5の環境にてpostgres_fdwを導入します
今回の完成形
本来postgres_fdwは異なるクラスタのDB(別サーバのPostgreSQL)はもちろん、別DBMS(OracleやMySQL等)とも接続できますが今回は同一クラスタ内の別DBにてDBリンクを実現しています。
(注意)ローカル側(以下例ではmyposdb)で発行されたSQLがそのままリモート側のDBにて実行されます。つまり、ローカル側とリモート側のスキーマ名+テーブル名は同じにする必要があります。
(参考情報ですがOracleではこのような制限はありません)
┌──────────┐ ┌─────────┐
│prdb │ │myposdb │
├──────────┤ ├─────────┤ sooniユーザにてmyposdbデータベースへログインし
│┌───────┐ │ │┌───────┐│ 以下SQLを実行する事で、prdbデータベースのkudamonoテーブル
││ public │ │ ││public ││ の値を参照できできるようにします。
│├───────┤ │ │├───────┤│ select * from public.kudamono;
││ │ │ ││ ││
││┌─────┐│ │ ││┌─────┐││
│││kudamono │<-udonman------│kudamono ││<--sooni
││└─────┘│ログイン ││└─────┘││ ログイン
││ table │ │ ││foreign table ││
│└───────┘ │ │└───────┘│
└──────────┘ └─────────┘
extentionモジュールを導入する
今回は何も問題なくextentionモジュールの導入はできたのですが、PostgreSQLをインストールしただけの環境などにおいては/usr/pgsql-xx/share/extension/配下に何も入っていない可能性があり、dnfコマンドを使って「contribモジュール(postgresqlxx-contrib)」をインストールする必要があります。その場合こちらの記事が参考になると思います。
現状のextentionモジュールの導入状況確認
以下はシステム権限を保有するユーザで実行します。当方環境はpostgresです。
extentionモジュールの導入はデータベース単位となります。今回のケースではmyposdbデータベースへextentionモジュールを導入する事になります。
[root@vm022 ~]# psql -h 192.168.3.22 -p 5432 -U postgres -d myposdb
ユーザ postgres のパスワード:
psql (13.6)
"help"でヘルプを表示します。
-- 導入前の確認
myposdb=# \dx
インストール済みの拡張一覧
名前 | バージョン | スキーマ | 説明
---------+------------+------------+--------------------------------------------------------------
dblink | 1.2 | public | connect to other PostgreSQL databases from within a database
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 行)
myposdb=#
extentionモジュールの導入
myposdb=# create extension postgres_fdw;
CREATE EXTENSION
-- 導入後の確認
myposdb=# \dx
インストール済みの拡張一覧
名前 | バージョン | スキーマ | 説明
--------------+------------+------------+--------------------------------------------------------------
dblink | 1.2 | public | connect to other PostgreSQL databases from within a database
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
postgres_fdw | 1.0 | public | foreign-data wrapper for remote PostgreSQL servers
(3 行)
myposdb=#
外部サーバ(リモートデータベース)サーバを定義する
ローカルサーバからリモートデータベースのテーブルをアクセスするためにローカルサーバ内に外部サーバを定義します。今回の例ではmyposdbデータベースにて実施します。
myposdb=# create server prdb_server foreign data wrapper postgres_fdw
myposdb-# options (host '192.168.3.22', port '5432', dbname 'prdb');
CREATE SERVER
myposdb=#
定義した外部サーバの確認SQL
select s.oid,s.srvname ,array_to_string(s.srvacl,CHR(10)) as srvacl ,array_to_string(array (select (pg_options_to_table(s.srvoptions))),CHR(10)) srvoptions from pg_catalog.pg_foreign_server s ;
定義した外部サーバの確認例
myposdb=# select s.oid,s.srvname
myposdb-# ,array_to_string(s.srvacl,CHR(10)) as srvacl
myposdb-# ,array_to_string(array (select (pg_options_to_table(s.srvoptions))),CHR(10)) srvoptions
myposdb-# from pg_catalog.pg_foreign_server s
myposdb-# ;
oid | srvname | srvacl | srvoptions
-------+-------------+---------------------+---------------------
16584 | prdb_server | postgres=U/postgres+| (host,192.168.3.22)+
| | sooni=U/postgres | (port,5432) +
| | | (dbname,prdb)
(1 row)
myposdb=#
ローカルユーザとリモートデータベースのユーザをマッピングする
リモートデータベースのテーブルをアクセスする際のログインユーザと、ローカルデータベースへログインするユーザのマッピングを行います。
CREATE USER MAPPINGにてリモートサーバのユーザをマッピングする
今回は同一クラスタ内、別DB間でのDBリンクなので「ユーザマッピング」という作業に違和感覚えるかもしれませんが、本来は別のRDBMSや、別クラスタDBに対するDBリンクを想定しているので当作業は必要になりますね。
create user mapping for sooni server prdb_server options (user 'udonman', password 'udonpass'); ;
ユーザのマッピング例
-- マッピング
myposdb=# create user mapping for sooni server prdb_server
myposdb-# options (user 'udonman', password 'udonpass')
myposdb-# ;
CREATE USER MAPPING
ユーザマッピング確認SQL
select pg_get_userbyid(umuser),s.srvname,array_to_string(m.umoptions,'/') from pg_user_mapping m inner join pg_foreign_server s on m.umserver = s.oid ;
ユーザマッピング確認例
-- マッピングの確認
pg_get_userbyid | srvname | array_to_string
-----------------+-------------+-------------------------------
sooni | prdb_server | user=udonman/password=udonpass
(1 row)
myposdb=#
先日Aurora(postgreSQL)を使っていて気付いたのですが、Auroraではpostgresユーザからもpg_user_mappingへアクセスする事ができません。代わりにpg_user_mappingsを使って確認する事になります。
myposdb=# select * from pg_user_mappings;
umid | srvid | srvname | umuser | usename | umoptions
-------+-------+-------------+--------+---------+---------------------------------
17155 | 16584 | prdb_server | 16384 | sooni | {user=udonman,password=udonman}
(1 行)
ユーザマッピングを削除する場合は以下
drop user mapping for sooni server prdb_server ;
外部サーバの定義を利用するユーザに対しアクセス権限を付与する
myposdb=# grant all on foreign server prdb_server to sooni;
GRANT
リモートDBのテーブルをローカルでも定義します
ここからは上記権限付与した一般ユーザ(sooni)で実行しています。
-- 通常のCREATE TABLE文と異なり最後にSERVER句が入ります
--
myposdb=> CREATE FOREIGN TABLE public.kudamono ( lno int,kana varchar(30),price int)
myposdb-> SERVER prdb_server;
CREATE FOREIGN TABLE
作成したテーブルを確認する
myposdb=> \d
リレーション一覧
スキーマ | 名前 | タイプ | 所有者
----------+----------------------+--------------+---------
public | dictionary_address | テーブル | udonman
public | ex010 | テーブル | udonman
public | ex03 | テーブル | udonman
public | kudamono | 外部テーブル | sooni
public | ptbl01 | テーブル | sooni
sooni | ex01 | テーブル | sooni
sooni | ex02 | テーブル | sooni
(7 行)
SQLを使ってより詳しく確認します
information_schema.foreign_tablesを参照する事でテーブル名と外部サーバ名の関係がわかります。
select r.rolname as owner ,n.nspname as schema ,c.relname as table_name , case when c.relkind = 'r' then 'table' when c.relkind = 'p' then 'partitioned table' when c.relkind = 'f' then 'foreign table' else concat(c.relkind,'') end relkind_dec ,foreign_server_name from pg_class c inner join pg_namespace n on c.relnamespace = n.oid inner join pg_roles r on c.relowner = r.oid left outer join information_schema.foreign_tables f on n.nspname = f.foreign_table_schema and relname = f.foreign_table_name where 1=1 and n.nspname not in ( 'pg_catalog','information_schema') and c.relkind in('r','p','f') -- オブジェクトタイプ order by schema,relname ;
owner | schema | table_name | relkind_dec | foreign_server_name
------------+-----------+----------------------+-------------------+---------------------
udonman | public | dictionary_address | table |
udonman | public | ex010 | table |
udonman | public | ex03 | table |
sooni | public | kudamono | foreign table | prdb_server
sooni | public | ptbl01 | table |
sooni | sooni | ex01 | table |
sooni | sooni | ex02 | table |
(7 行)
実際にアクセスしてみる
通常のテーブルと同様に使えます
myposdb=> select * from kudamono;
lno | kana | price
-----+--------+-------
1 | リンゴ | 120
(1 行)
myposdb=> insert into kudamono(lno,kana,price) values (2,'バナナ',280);
INSERT 0 1
myposdb=> select * from kudamono;
lno | kana | price
-----+--------+-------
1 | リンゴ | 120
2 | バナナ | 280
(2 行)
myposdb=>
セッションを確認する
一度アクセスするとセッションは残ります。基本的に毎回接続し直すdblinkと異なり2回目以降のアクセスコストが下がります。
myposdb=> select pid,state,datname,application_name,usename
myposdb-> ,to_char(backend_start,'YYYY-MM-DD HH24:MI:SS') backend_start
myposdb-> from pg_stat_activity where client_addr is not null
myposdb-> order by backend_start;
pid | state | datname | application_name | usename | backend_start
------+---------------------+---------+--------------------------+---------+---------------------
7322 | active | myposdb | pgAdmin 4 - DB:myposdb | sooni | 2022-04-17 19:37:03
7388 | active | myposdb | psql | sooni | 2022-04-17 19:42:47
7403 | idle | prdb | postgres_fdw | sooni | 2022-04-17 19:43:50
7440 | idle in transaction | myposdb | pgAdmin 4 - CONN:8799663 | sooni | 2022-04-17 19:46:17
(4 行)
myposdb=>