Es gibt viele Dinge, die mir an postgres ausgesprochen gut gefallen: die Datenbank hat einen sehr ausgereiften SQL-Dialekt, eine erstaunliche Erweiterbarkeit, zahllose Datentypen und mit psql ein sehr schönes command-line interface. Wenn ich allerdings darüber nachdenke, was mir an diesem RDBMS weniger gut gefällt, dann gehört dazu die Tatsache, dass die Datenbanken eines postgres Clusters ganz strikt voneinander getrennt sind, da ihre data dictionaries nichts voneinander wissen. Das FAQ im PostgreSQL Wiki sagt dazu: "There is no way to directly query a database other than the current one. Because PostgreSQL loads database-specific system catalogs, it is uncertain how a cross-database query should even behave." Wahrscheinlich kann man das Verhalten also theoretisch begründen - gefallen muss es mir deshalb aber noch nicht. Immerhin gibt es aber zwei Möglichkeiten, diese Beschränkung zu umgehen: die Verwendung von database links und den Einsatz eines foreign data wrappers.
Database Link
Die ältere Lösung ist dabei der database link, der es ermöglicht, eine Query zu definieren, die in einer entfernten Datenbank ausgeführt wird. Dabei gibt man eine Connection an und ergänzt das auszuführende Statement sowie eine Typ-Beschreibung der Ergebnisspalten. Dazu ein kleines Beispiel. Zunächst lege ich drei Datenbanken an, von denen zwei die Rolle von Quellsystemen spielen, während die dritte als Reporting-Datenbank fungiert. Im Beispiel liegen alle Datenbanken im gleichen postgres-Cluster (also dem, was man in anderen RDBMS als Instanz bezeichnet), aber sie könnten auch auf entfernten Rechnern und in unterschiedlichen Clustern liegen. Aus Gründen der Vereinfachung habe ich mich auch nicht um eine plausible Benutzerzuordnung gekümmert, sondern agiere überall als postgres:-- Anlage von Test-Datenbanken -- Reporting-DB postgres=# create database dbadmin; CREATE DATABASE -- Quell-DBs postgres=# create database t1; CREATE DATABASE postgres=# create database t2; CREATE DATABASE -- Quell-Tabellen t1=# create table t1_tab1 as select generate_series(1, 10) id; SELECT 10 t2=# create table t2_tab2 as select generate_series(1, 100) id; SELECT 100
Dazu nun ein Zugriff über database link:
dbadmin=# SELECT * dbadmin-# FROM dblink('dbname=t1 user=postgres password=XXXXXX' dbadmin-# , 'select id from t1_tab1') dbadmin-# AS t1(id int) dbadmin-# ; id ---- 1 2 3 4 5 6 7 8 9 10 (10 Zeilen)
In diesem kleinen Beispiel ist das Verfahren noch relativ handhabbar, aber für größere Queries kann es recht mühsam werden, die Spaltendefinitionen aufzuführen. Die Dokumentation empfiehlt, solche database links in Views zu packen, aber beim Zugriff auf eine größere Anzahl von Datenbanken wird auch diese Vorgehensweise sperrig.
Foreign Data Wrapper
Zur Vereinfachung des Vorgehens (und aus einer Reihe weiterer Gründe wie Transaktionsmanagement, Connection-Management und Performance, die in der Dokumentation detailliert erläutert werden) wurde mit postgres 9.3 die extension postgres_fdw in den Standard aufgenommen (für 9.2 gab es einen Backport). Diese Erweiterung erlaubt die Definition umfangreicherer statischer Objekte, die den Zugriff auf entfernte Datenbanken vereinfachen. Das folgende Beispiel basiert auf den Datenbank- und Tabellendefinitionen des database link Beispiels:
-- Installation der extension dbadmin=# create extension postgres_fdw; CREATE EXTENSION -- Anlage von Metadaten-Objekten in der Reporting-Datenbank (DBADMIN) -- Definition der Server dbadmin=# CREATE SERVER fdw_t1 FOREIGN DATA WRAPPER postgres_fdw dbadmin=# OPTIONS (dbname 't1', host 'localhost'); CREATE SERVER dbadmin=# CREATE SERVER fdw_t2 FOREIGN DATA WRAPPER postgres_fdw dbadmin=# OPTIONS (dbname 't2', host 'localhost'); CREATE SERVER -- Definition von user-mappings dbadmin=# CREATE USER MAPPING for postgres dbadmin-# SERVER fdw_t1 dbadmin-# OPTIONS (user 'postgres', password 'XXXXXX'); CREATE USER MAPPING dbadmin=# CREATE USER MAPPING for postgres dbadmin-# SERVER fdw_t2 dbadmin-# OPTIONS (user 'postgres', password 'XXXXXX'); CREATE USER MAPPING -- Definition von foreign tables dbadmin=# CREATE FOREIGN TABLE t1_tab1 dbadmin-# ( dbadmin(# id integer dbadmin(# ) dbadmin-# SERVER fdw_t1 OPTIONS (table_name 't1_tab1'); CREATE FOREIGN TABLE dbadmin=# CREATE FOREIGN TABLE t2_tab2 dbadmin-# ( dbadmin(# id integer dbadmin(# ) dbadmin-# SERVER fdw_t2 OPTIONS (table_name 't2_tab2'); CREATE FOREIGN TABLE -- eine übergreifende Abfrage auf Zieltabellen
-- aus zwei unterschiedlichen Datenbanken dbadmin-# select 't2_tab2' table_name, count(*) cnt from t2_tab2; table_name | cnt ------------+----- t1_tab1 | 10 t2_tab2 | 100 (2 Zeilen)
Natürlich bleibt auch in diesem Fall das Problem, dass man zur Anlage der foreign tables deren Definitionen im Quellsystem kennen muss, aber um diese Information zu bekommen, könnte man zusätzlich eine foreign table für information_schema.tables einrichten. Nützlich ist dabei auch, dass die Definition für eine foreign table nicht alle Spalten der Quelltabelle enthalten muss, so dass man sich auf die relevanten Angaben beschränken kann:
dbadmin=# CREATE FOREIGN TABLE t2_columns dbadmin-# ( dbadmin(# table_name text dbadmin(# , column_name text dbadmin(# , data_type text dbadmin(# ) dbadmin-# SERVER fdw_t2 OPTIONS (schema_name 'information_schema', table_name 'columns'); CREATE FOREIGN TABLE dbadmin=# select * from t2_columns limit 10; table_name | column_name | data_type --------------+-------------+----------- pg_statistic | starelid | oid pg_statistic | staattnum | smallint pg_statistic | stainherit | boolean pg_statistic | stanullfrac | real pg_statistic | stawidth | integer pg_statistic | stadistinct | real pg_statistic | stakind1 | smallint pg_statistic | stakind2 | smallint pg_statistic | stakind3 | smallint pg_statistic | stakind4 | smallint (10 Zeilen)
Ich will nicht behaupten, dass damit alle Probleme gelöst wären - und finde immer noch, dass die entsprechenden Optionen anderer RDBMS (z.B. database links bei Oracle, linked server im SQL Server) etwas besser zu handhaben sind, aber ich denke, dass postgres_fdw einen signifikanten Fortschritt darstellt.
Übrigens fällt mir gerade noch ein Punkt ein, der mich bei postgres deutlich mehr stört als der gerade ausgeführte, nämlich das Fehlen detaillierter Informationen zu den ausgeführten Queries und ihrer Performance. Aber das ist wieder einmal eine Geschichte, die ein andermal erzählt werden soll.
Keine Kommentare:
Kommentar veröffentlichen