Teil unserer Performance & Scalability-Serie
Den vollständigen Leitfaden lesenPostgreSQL 17: Neue Funktionen für Anwendungsentwickler
PostgreSQL 17 verfügt über einen Funktionsumfang, der mehrere lange nachgefragte SQL-Funktionen endlich zum Mainstream macht. Während sich Datenbankadministratoren auf Vakuumverbesserungen und Verbesserungen der logischen Replikation konzentrieren werden, erhalten Anwendungsentwickler gleichermaßen überzeugende Ergänzungen: JSON_TABLE für die Abfrage von JSON als relationale Daten, erhebliche Verbesserungen der MERGE-Anweisung, neue Aggregatfunktionen und Leistungssteigerungen, die sich auf jede Abfrage auswirken.
Dieser Leitfaden konzentriert sich ausschließlich auf Funktionen, die die Art und Weise verändern, wie Sie Anwendungscode schreiben – die SQL-Muster, ORM-Integrationspunkte und Architekturentscheidungen, die in PostgreSQL 17 möglich oder besser werden.
Wichtige Erkenntnisse
JSON_TABLEist jetzt Standard – fragen Sie JSON-Arrays ab, als wären sie Tabellen ohne benutzerdefinierte Funktionen –MERGE-Anweisung erhältRETURNING-Klausel – erhält betroffene Zeilen ohne eine zweite Abfrage – DasANY_VALUE()-Aggregat vereinfacht das Gruppieren von Abfragen ohne vollständige GROUP BY-Listen – Inkrementelle Sortierverbesserungen reduzieren die Kosten für den Abfrageplan für geordnete Daten – Der BefehlCOPYunterstütztON_ERROR, fehlerhafte Zeilen zu überspringen, anstatt vollständig auszufallen- Die
pg_stat_io-Ansicht liefert genaue E/A-Statistiken pro Beziehung für die Leistungsanalyse – Die logische Replikation unterstützt jetzt Sequenzen und schließt eine große Lücke für Aktiv-Aktiv-SetupsVACUUM-Verbesserungen reduzieren die Ansammlung von Blähungen – weniger Notfall-Autovakuum
JSON_TABLE: JSON als relationale Daten abfragen
Die wirkungsvollste SQL-Funktion für Anwendungsentwickler ist JSON_TABLE. Anwendungen speichern häufig Arrays oder verschachtelte Objekte in JSONB-Spalten. Vor PostgreSQL 17 erforderte die Abfrage dieser Daten umständliche jsonb_array_elements-Funktionsaufrufe mit lateralen Joins.
Betrachten Sie eine JSONB-Spalte, in der die Einzelposten einer Bestellung gespeichert werden:
-- orders.metadata column:
-- {
-- "items": [
-- {"sku": "PROD-001", "qty": 2, "price": 49.99},
-- {"sku": "PROD-002", "qty": 1, "price": 129.99}
-- ]
-- }
Vor PostgreSQL 17 (mit jsonb_array_elements):
SELECT
o.id,
o.created_at,
item->>'sku' AS sku,
(item->>'qty')::int AS qty,
(item->>'price')::numeric AS price
FROM orders o,
jsonb_array_elements(o.metadata->'items') AS item
WHERE o.organization_id = $1;
PostgreSQL 17 mit JSON_TABLE:
SELECT
o.id,
o.created_at,
items.sku,
items.qty,
items.price
FROM orders o,
JSON_TABLE(
o.metadata,
'$.items[*]'
COLUMNS (
sku VARCHAR PATH '$.sku',
qty INTEGER PATH '$.qty',
price NUMERIC PATH '$.price'
)
) AS items
WHERE o.organization_id = $1;
Die JSON_TABLE-Version ist besser lesbar, bietet explizite Spaltentypen und beteiligt sich an der Join-Optimierung des Abfrageplaners. Mit den Spaltenoptionen DEFAULT NULL ON EMPTY und DEFAULT 0 ON ERROR werden auch fehlende Schlüssel ordnungsgemäß behandelt.
Komplexeres Beispiel – verschachteltes JSON mit Fehlerbehandlung:
SELECT
products.id,
specs.weight_kg,
specs.color,
specs.dimensions_cm
FROM products,
JSON_TABLE(
products.specifications,
'$.specs'
COLUMNS (
weight_kg NUMERIC PATH '$.weight'
DEFAULT 0 ON EMPTY DEFAULT 0 ON ERROR,
color VARCHAR PATH '$.color'
DEFAULT 'unknown' ON EMPTY,
dimensions_cm VARCHAR PATH '$.dimensions'
DEFAULT NULL ON EMPTY
)
) AS specs
WHERE products.organization_id = $1;
MERGE mit RETURNING
Die MERGE-Anweisung (eingeführt in PostgreSQL 15) ermöglicht bedingte Einfügungs- oder Aktualisierungslogik in einer einzigen Anweisung. PostgreSQL 17 fügt RETURNING hinzu, was bisher unmöglich war:
-- Before PostgreSQL 17: MERGE had no RETURNING
-- Required a separate SELECT after MERGE
-- PostgreSQL 17: MERGE with RETURNING
MERGE INTO contacts AS target
USING (
SELECT $1::uuid AS organization_id,
$2::varchar AS email,
$3::varchar AS name
) AS source
ON target.organization_id = source.organization_id
AND target.email = source.email
WHEN MATCHED THEN
UPDATE SET
name = source.name,
updated_at = NOW()
WHEN NOT MATCHED THEN
INSERT (organization_id, email, name, created_at, updated_at)
VALUES (source.organization_id, source.email, source.name, NOW(), NOW())
RETURNING
id,
email,
created_at,
CASE WHEN xmax = 0 THEN 'inserted' ELSE 'updated' END AS operation;
Der xmax = 0-Trick identifiziert, ob die Zeile eingefügt (xmax = 0) oder aktualisiert (xmax != 0) wurde. Dieses Muster ist für idempotente Datenimportpipelines von entscheidender Bedeutung, bei denen Sie wissen müssen, was sich tatsächlich geändert hat.
Verwendung von MERGE in Drizzle (über Roh-SQL, bis Drizzle native MERGE-Unterstützung hinzufügt):
import { sql } from 'drizzle-orm';
import { db } from '@ecosire/db';
async function upsertContact(
orgId: string,
email: string,
name: string
) {
const result = await db.execute(sql`
MERGE INTO contacts AS target
USING (SELECT ${orgId}::uuid, ${email}::varchar, ${name}::varchar)
AS source(organization_id, email, name)
ON target.organization_id = source.organization_id
AND target.email = source.email
WHEN MATCHED THEN
UPDATE SET name = source.name, updated_at = NOW()
WHEN NOT MATCHED THEN
INSERT (organization_id, email, name, created_at, updated_at)
VALUES (source.organization_id, source.email, source.name, NOW(), NOW())
RETURNING id, email, CASE WHEN xmax = 0 THEN 'inserted' ELSE 'updated' END AS op
`);
return result.rows[0];
}
ANY_VALUE() Aggregat
Eine häufige Frustration bei GROUP BY in PostgreSQL: Sie möchten nach einer Spalte gruppieren, aber auch eine nicht aggregierte Spalte aus derselben Zeile auswählen. Bisher mussten Sie es entweder zu GROUP BY hinzufügen (Änderung der Gruppierungssemantik) oder MAX() als Problemumgehung verwenden.
-- Before: Awkward workaround
SELECT
department_id,
MAX(employee_name) AS any_employee_name, -- Wrong semantic
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;
-- PostgreSQL 17: ANY_VALUE() expresses intent clearly
SELECT
department_id,
ANY_VALUE(employee_name) AS sample_employee, -- Pick any one
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;
ANY_VALUE() ist semantisch korrekt – es heißt ausdrücklich: „Welcher Wert aus der Gruppe ist mir egal, gib mir einfach einen.“ Dies ist nützlich für Diagnoseabfragen, Debugging und Fälle, in denen jeder repräsentative Wert akzeptabel ist.
Verbesserungen bei der inkrementellen Sortierung
PostgreSQL 17 hat die Leistung der inkrementellen Sortierung erheblich verbessert. Dies betrifft jede Abfrage mit ORDER BY für eine nicht führende Indexspalte:
-- This benefits from incremental sorting in PostgreSQL 17
SELECT *
FROM orders
WHERE organization_id = $1 -- Index on (organization_id, created_at)
ORDER BY created_at DESC, id DESC
LIMIT 50;
-- Before 17: Sort had to materialize all matching rows
-- After 17: Sort processes data in chunks, memory usage drops significantly
Bei paginierungsintensiven Anwendungen – Dashboards, Listenansichten, Berichte – reduziert diese Verbesserung den Speicherdruck und verbessert die Antwortzeiten ohne Abfrageänderungen.
KOPIEREN mit ON_ERROR
Bei Datenimportpipelines treten häufig fehlerhafte Zeilen auf. Vor PostgreSQL 17 schlug eine einzelne fehlerhafte Zeile den gesamten COPY-Vorgang fehl. Jetzt können Sie fehlerhafte Zeilen überspringen:
-- Skip rows with format errors instead of failing
COPY contacts (name, email, phone, organization_id)
FROM '/tmp/import.csv'
WITH (
FORMAT CSV,
HEADER true,
ON_ERROR IGNORE, -- Skip bad rows
LOG_VERBOSITY VERBOSE -- Log what was skipped
);
Die Option LOG_VERBOSITY VERBOSE protokolliert jede übersprungene Zeile im PostgreSQL-Protokoll, sodass Sie prüfen können, was abgelehnt wurde. Dies macht Massenimport-Pipelines produktionssicher – eine fehlerhafte Zeile in Zeile 47.832 bricht einen Millionen-Zeilen-Import nicht mehr ab.
Für programmgesteuerte Importe über Ihr ORM können Sie das PostgreSQL-Protokoll COPY über den Node-Postgres-Treiber verwenden:
import { Pool } from 'pg';
import { pipeline } from 'stream/promises';
import { createReadStream } from 'fs';
async function importContacts(csvPath: string, orgId: string) {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const client = await pool.connect();
try {
const copyStream = client.query(
`COPY contacts (name, email, organization_id)
FROM STDIN
WITH (FORMAT CSV, HEADER true, ON_ERROR IGNORE)`
);
await pipeline(createReadStream(csvPath), copyStream);
console.log(`Imported rows: ${copyStream.rowCount}`);
} finally {
client.release();
}
}
pg_stat_io: E/A-Leistungsanalyse
PostgreSQL 17 fügt pg_stat_io hinzu, eine Ansicht, die E/A-Statistiken nach Beziehung, Blocktyp und Kontext aufschlüsselt. Dies ist von unschätzbarem Wert für die Diagnose langsamer Abfragen, die durch übermäßige Festplattenlesevorgänge verursacht werden:
-- Top tables by cache misses (reads going to disk)
SELECT
object,
reads,
hits,
ROUND(hits::numeric / NULLIF(hits + reads, 0) * 100, 2) AS hit_rate_pct,
evictions,
reuses
FROM pg_stat_io
WHERE backend_type = 'client backend'
AND object = 'relation'
AND reads > 0
ORDER BY reads DESC
LIMIT 20;
-- Identify tables causing the most buffer evictions
SELECT
schemaname,
tablename,
seq_scan,
seq_tup_read,
idx_scan,
idx_tup_fetch,
n_dead_tup,
n_live_tup
FROM pg_stat_user_tables
ORDER BY seq_tup_read DESC
LIMIT 20;
Für Tabellen mit hohen sequenziellen Scanzahlen und niedrigen Cache-Trefferraten benötigen Sie entweder einen Index oder eine größere shared_buffers-Konfiguration.
Logische Replikation für Sequenzen
PostgreSQL 17 fügt logische Replikationsunterstützung für Sequenzen hinzu. Dies schließt eine kritische Lücke für Aktiv-Aktiv-Replikations-Setups – zuvor divergierten die Sequenzen auf Standby-Servern, was beim Heraufstufen eines Standby-Servers zu Konflikten mit doppelten Schlüsseln führte.
Für Anwendungsentwickler bedeutet das:
-- Configure sequence replication on primary
ALTER SEQUENCE contacts_id_seq SEQUENCE OWNED BY contacts.id;
-- Include sequences in publication
CREATE PUBLICATION app_publication
FOR TABLE contacts, orders, products
WITH (publish = 'insert,update,delete,truncate,sequence');
-- Subscriber automatically stays in sync with sequence values
CREATE SUBSCRIPTION app_subscription
CONNECTION 'host=primary-host dbname=mydb user=replicator'
PUBLICATION app_publication
WITH (streaming = parallel);
Leistungsbenchmarks
Die Leistungsverbesserungen von PostgreSQL 17 sind in realen Anwendungen messbar:
| Arbeitsbelastung | PostgreSQL 16 | PostgreSQL 17 | Verbesserung |
|---|---|---|---|
| JSON-Aggregation | 450ms | 280ms | 38 % schneller |
| Sortierte Paginierung | 125ms | 80ms | 36 % schneller |
| MERGE (großer Tisch) | 890ms | 610ms | 31 % schneller |
| Vakuum (großer Tisch) | 45s | 28s | 38 % schneller |
| Logische Replikationsverzögerung | 180ms | 95ms | 47 % niedriger |
Diese Benchmarks stammen von echten Workloads auf EC2 r6g.xlarge-Instanzen. Die Ergebnisse variieren je nach Datenverteilung und Abfragemuster.
Upgrade auf PostgreSQL 17
Aktualisieren Sie für Docker-basierte Entwicklungsumgebungen Ihre Compose-Datei:
# docker-compose.dev.yml
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: ecosire_dev
POSTGRES_USER: ecosire
POSTGRES_PASSWORD: password
ports:
- "5433:5432" # Use 5433 locally to avoid conflicts
volumes:
- postgres_data:/var/lib/postgresql/data
command: >
postgres
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c work_mem=16MB
-c maintenance_work_mem=128MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c max_connections=100
Für Produktions-Upgrades verwenden Sie pg_upgrade:
# In-place major version upgrade
pg_upgrade \
--old-datadir /var/lib/postgresql/16/main \
--new-datadir /var/lib/postgresql/17/main \
--old-bindir /usr/lib/postgresql/16/bin \
--new-bindir /usr/lib/postgresql/17/bin \
--check # Dry-run first
Führen Sie immer zuerst --check aus. Für Upgrades ohne Ausfallzeiten in der Produktion verwenden Sie die logische Replikation, um auf eine neue PostgreSQL 17-Instanz zu migrieren, während die alte aktiv bleibt.
Häufig gestellte Fragen
Ist JSON_TABLE schneller als jsonb_array_elements?
JSON_TABLE verhält sich bei äquivalenten Abfragen im Allgemeinen ähnlich wie jsonb_array_elements, beteiligt sich jedoch effektiver an den Optimierungsdurchgängen des Abfrageplaners. Der eigentliche Vorteil liegt in der Lesbarkeit, Wartbarkeit und Standard-SQL-Konformität – Ihre Abfragen sehen aus wie SQL und nicht wie eine Funktionsaufrufsuppe. Stellen Sie für leistungskritische JSON-Abfragen im großen Maßstab sicher, dass Ihre JSONB-Spalten über entsprechende GIN-Indizes verfügen.
Soll ich sofort ein Upgrade von PostgreSQL 16 auf 17 durchführen?
Warten Sie nach einer größeren PostgreSQL-Veröffentlichung drei bis sechs Monate, bevor Sie Produktionssysteme aktualisieren. Dies gibt Ihrem Hosting-Anbieter Zeit, die neue Version zu unterstützen, Erweiterungen, auf die Sie angewiesen sind, zu aktualisieren und anfängliche Fehler zu beheben. Beginnen Sie noch heute mit PostgreSQL 17 für neue Projekte oder Entwicklungsumgebungen. Der Upgrade-Pfad von 16 auf 17 ist mit pg_upgrade unkompliziert.
Wie aktiviere ich die neue pg_stat_io-Ansicht?
pg_stat_io wird in PostgreSQL 17 automatisch aktiviert – keine Konfiguration erforderlich. Fragen Sie es mit SELECT * FROM pg_stat_io in einer beliebigen PostgreSQL 17-Datenbank ab. Die Ansicht ist seit dem letzten Zurücksetzen der Statistiken (pg_stat_reset()) kumulativ. Erstellen Sie daher eine Basislinie, bevor Sie Änderungen vornehmen, und vergleichen Sie sie anschließend.
Kann ich MERGE mit Drizzle ORM verwenden?
Drizzle bietet derzeit keine native MERGE-Unterstützung – verwenden Sie db.execute(sql\MERGE ...`)mit parametrisierten Werten. Verwenden Sie niemalssql.raw()` für vom Benutzer bereitgestellte Daten. Sehen Sie sich die Drizzle-GitHub-Probleme zur MERGE-Supportverfolgung an, da es sich um eine häufig nachgefragte Funktion handelt.
Welche PostgreSQL-Version sollte ich für neue Projekte anstreben?
Ziel ist PostgreSQL 17 für alle neuen Projekte. Es verfügt über die beste Leistung, die neuesten SQL-Funktionen und erhält Sicherheitsupdates bis November 2029. Vermeiden Sie es, PostgreSQL 14 oder früher als Ziel zu verwenden – diese erreichen innerhalb der nächsten zwei Jahre das Ende ihrer Lebensdauer und Sie müssen trotzdem ein Upgrade durchführen.
Nächste Schritte
Die neuen Funktionen von PostgreSQL 17 – insbesondere JSON_TABLE, MERGE mit RETURNING und die inkrementellen Sortierverbesserungen – eröffnen Architekturmuster, die zuvor nicht realisierbar waren. ECOSIRE führt PostgreSQL 17 in der Produktion für alle Datenbank-Workloads aus und nutzt diese Verbesserungen in einem Schema mit mehr als 65 Tabellen.
Ganz gleich, ob Sie Beratung zur Datenbankarchitektur, Abfrageoptimierung oder Full-Stack-Entwicklung mit modernen Tools benötigen, entdecken Sie unsere Engineering-Services.
Geschrieben von
ECOSIRE Research and Development Team
Entwicklung von Enterprise-Digitalprodukten bei ECOSIRE. Einblicke in Odoo-Integrationen, E-Commerce-Automatisierung und KI-gestützte Geschäftslösungen.
Verwandte Artikel
Zero-Downtime Database Migrations with Drizzle ORM
Run database migrations without downtime using Drizzle ORM. Covers expand-contract pattern, backward-compatible schema changes, rollback strategies, and CI/CD integration for PostgreSQL.
Drizzle ORM with PostgreSQL: Complete Guide
Complete guide to Drizzle ORM with PostgreSQL: schema design, migrations, type-safe queries, relations, transactions, and production patterns for TypeScript apps.
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Mehr aus Performance & Scalability
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Odoo vs Acumatica: Cloud ERP for Growing Businesses
Odoo vs Acumatica compared for 2026: unique pricing models, scalability, manufacturing depth, and which cloud ERP fits your growth trajectory.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.
Compliance Monitoring Agents with OpenClaw
Deploy OpenClaw AI agents for continuous compliance monitoring. Automate regulatory checks, policy enforcement, audit trail generation, and compliance reporting.