PostgreSQL kennt zwei eng zusammenhängende Konzepte: Encoding legt fest, wie Bytes zu Zeichen werden (UTF8, LATIN1, …), Collation legt fest, in welcher Reihenfolge diese Zeichen sortiert und wie sie verglichen werden. Auf modernen Setups ist UTF8 die einzig sinnvolle Wahl beim Encoding — die Collation-Frage hingegen hat einen unterschätzten Stolperstein, der nach OS-Upgrades aufschlägt.
Encoding pro Datenbank
Postgres legt das Encoding pro Datenbank fest. Das gesamte Cluster läuft mit einem einheitlichen template0/template1-Default, jede neue Datenbank erbt das — kann es aber überschreiben.
SHOW server_encoding;
-- server_encoding
-- -----------------
-- UTF8
SELECT datname, pg_encoding_to_char(encoding), datcollate, datctype
FROM pg_database;Empfehlung: UTF8 für alles. Latin1, SQL_ASCII oder andere Legacy-Encodings sind nur dann sinnvoll, wenn man Daten aus uralten Systemen importiert und Konvertierung nicht möglich ist. Selbst dann ist die übliche Strategie: importieren, konvertieren, in UTF8-DB ablegen.
SQL_ASCII ist eine besondere Falle: Postgres prüft dabei gar nichts, sondern speichert Bytes wie sie kommen. Mischformate, ungültige Sequenzen, schief codierte Strings — alles möglich. Niemals SQL_ASCII für neue Datenbanken.
Encoding beim CREATE DATABASE setzen
CREATE DATABASE myapp
WITH OWNER = app_owner
ENCODING = 'UTF8'
LC_COLLATE = 'C.UTF-8'
LC_CTYPE = 'C.UTF-8'
TEMPLATE = template0;Wichtig: das TEMPLATE = template0 ist nicht optional, wenn du Encoding/Collation abweichend vom Cluster-Default setzen willst. template1 (der Default) erbt seine Settings vom Cluster und blockiert, wenn du andere wählst. template0 ist eine pristine Kopie ohne Modifikationen — von dort darf man frei wählen.
Collation — Sortier- und Vergleichsregeln
Collation definiert, wie Strings sortiert und verglichen werden. ORDER BY name mit deutscher Collation sortiert „Ä” anders als mit englischer.
PostgreSQL kennt zwei Provider für Collations:
| Provider | Beschreibung | Vor-/Nachteile |
|---|---|---|
| libc | Nutzt die System-libc-Locales (auf Linux: glibc). | Universell verfügbar, aber: Sortierung ändert sich zwischen libc-Versionen → potenziell Index-Korruption nach OS-Upgrade. |
| ICU | International Components for Unicode (eigene Library). | Stabile, versionierte Collations; OS-unabhängig. Empfohlen seit PG 13, Default seit PG 16. |
Auf einer modernen Postgres-Installation (PG 16+) kommt UTF8+ICU als Default. Auf älteren Installationen oder bei Upgrade von altem Datenbestand bleibt häufig libc — und das ist die Quelle eines klassischen Problems:
Der „collation version mismatch“-Bug
Wenn du Postgres mit libc-Collations betreibst und das Betriebssystem auf eine neue glibc-Version upgrade’st, kann sich die Sortierordnung von Strings ändern. Postgres bemerkt das und gibt eine Warnung:
WARNING: collation "de_DE.UTF-8" has version mismatch
DETAIL: The collation in the database was created using version 2.31,
but the operating system provides version 2.35.
HINT: Rebuild all objects affected by this collation and run
ALTER COLLATION pg_catalog."de_DE.UTF-8" REFRESH VERSION,
or build PostgreSQL with the right library version.Konkret heißt das: Indexe, die auf string-vergleichenden Collations basieren (im Wesentlichen alle B-tree-Indexe auf text/varchar), könnten falsche Daten enthalten — ihre interne Reihenfolge passt nicht mehr zur tatsächlichen libc-Sortierung. Ein WHERE name = 'foo' kann Treffer übersehen.
Dieses Risiko betrifft besonders:
- Container-Migrationen (
postgres:14-bullseye→postgres:14-bookworm) - OS-Upgrades auf der Datenbank-Maschine (Ubuntu 22.04 → 24.04)
- Cross-Plattform-Restores (Linux → macOS oder umgekehrt)
Lösung 1: Indexe mit string-Spalten neu aufbauen.
REINDEX DATABASE myapp;Lösung 2 (besser, langfristig): auf ICU-Collations umstellen — die sind versioniert und ändern sich bei OS-Upgrades nicht.
ICU-Collations nutzen
Datenbank gleich mit ICU anlegen:
CREATE DATABASE myapp
WITH OWNER = app_owner
ENCODING = 'UTF8'
LOCALE_PROVIDER = 'icu'
ICU_LOCALE = 'de-DE'
TEMPLATE = template0;Eigene ICU-Collations definieren und auf Spaltenebene nutzen:
CREATE COLLATION german_phonebook (
provider = icu,
locale = 'de-u-co-phonebk'
);
CREATE TABLE contacts (
id serial primary key,
name text COLLATE german_phonebook
);german_phonebook sortiert „Ä” wie „Ae” — typische deutsche Telefonbuch-Sortierung. ICU-Locale-Strings folgen BCP 47 und sind sehr ausdrucksstark.
Die „magische” C.UTF-8-Collation
Eine besondere Variante: C.UTF-8 (oder schlicht C) sortiert byte-weise, nicht sprachsensitiv. Vorteile:
- Schneller als Sprach-Collations (kein Unicode-Lookup nötig)
- Stabil (keine Versions-Probleme)
- Funktioniert mit
LIKE '...%'-Patterns auf Index-Ebene direkt (sprachsensitive Collations brauchen ofttext_pattern_ops-Index)
Nachteile:
- Sortierung ist „A-Z, dann a-z” und „Ä” hinter „Z” — nicht das, was Menschen erwarten
Faustregel: für rein technische Daten (UUIDs, E-Mail-Adressen, URLs, Slug-Felder) ist C.UTF-8 die beste Wahl. Für menschenlesbare Namen, Titel, Adressen lohnen sich sprachsensitive ICU-Collations.
Du kannst beides mischen — die DB-Default-Collation ist eine Sache, einzelne Spalten können ihre eigene Collation haben:
CREATE TABLE users (
id serial primary key,
email text COLLATE "C.UTF-8", -- E-Mail: byte-weise sortieren
name text COLLATE "de-DE-x-icu" -- Name: deutsch sortieren
);Häufige Stolperfallen
SQL_ASCII speichert Bytes ohne Validierung — niemals fuer neue DBs.
Postgres prüft bei SQL_ASCII weder Encoding noch Korrektheit. Du kannst gleichzeitig UTF8- und Latin1-Bytes reinschreiben — und Wochen später beim Lesen kracht es. Wer migriert, wandelt vorher um, statt SQL_ASCII zu wählen.
Encoding in CREATE DATABASE braucht TEMPLATE = template0.
Wer ein abweichendes Encoding angibt und vergisst TEMPLATE = template0, bekommt den Fehler „new encoding is incompatible with the encoding of the template database”. Lösung: explizit aus template0 clonen.
libc-Collations koennen Indexe nach OS-Upgrade brechen.
Klassisches Phänomen: nach apt full-upgrade melden sich Postgres mit „collation version mismatch”. Wer jetzt nichts tut, riskiert Datenverlust durch falsche Index-Treffer. Sofortiger REINDEX DATABASE ist die Brandschutz-Aktion; langfristig auf ICU umstellen.
ICU-Locale-Strings nutzen Bindestrich, libc-Locales Unterstrich.
ICU: de-DE. libc: de_DE.UTF-8. Wer das verwechselt, bekommt „collation does not exist”. Postgres validiert die Locale-Strings beim CREATE — Tippfehler werden nicht verziehen.
CREATE DATABASE mit Encoding setzt nicht automatisch die Collation.
ENCODING = 'UTF8' allein reicht nicht. Wenn dein Cluster-Default eine andere Collation hat (etwa C von initdb ohne Locale), erbt die neue DB diese — auch mit UTF8-Encoding. LC_COLLATE und LC_CTYPE immer mit setzen, wenn das Verhalten relevant ist.
LIKE-Performance mit Sprach-Collation ist eine eigene Falle.
WHERE name LIKE 'foo%' nutzt einen normalen B-tree-Index nur dann, wenn die Collation C oder C.UTF-8 ist. Bei sprachsensitiven Collations braucht’s einen Index mit text_pattern_ops. Falls du beides willst (sprachsensitive Sortierung + schnelle Prefix-Suche), brauchst du zwei Indexe.
Weiterführende Ressourcen
Externe Quellen
- Localization – PostgreSQL Documentation
- Character Set Support
- Collation Support
- CREATE DATABASE – PostgreSQL Documentation
- PostgreSQL Wiki: Locale data changes
- BCP 47 / Unicode Locale Identifiers