Base64-Dekodierung in SQL: Ein vollständiger Leitfaden
Öffnen Sie lange genug irgendeine Produktionsdatenbank, und Sie werden die Verkleidung antreffen. Ein Avatar, der als Mauer aus Buchstaben in einem JSON-Export ankam. Ein JWT, das in einer varchar-Spalte neben einer Benutzer-ID parkt. Ein Zertifikat, das jemand als String verschicken wollte, weil das Übertragungsformat kein Binärformat bot. Irgendwo in einer Tabelle tragen Ihre Daten Buchstaben, und Ihr Job ist es, sie auszuziehen, ohne die Datenbank zu verlassen.
In SQL hat dieser Job eine sehr beruhigende Eigenschaft: Sobald Sie wissen, welchen Dekodierer Ihr Dialekt spricht, schrumpft der ganze Job auf einen einzelnen Funktionsaufruf. Das Format selbst wird auf der Startseite bereits im Detail erklärt (64 druckbare Zeichen, jede Vierergruppe steht für drei Eingabe-Bytes, bis zu zwei =-Zeichen Padding in der letzten Gruppe), also spart sich dieser Artikel die Vorlesung. Zwei Dinge nehmen Sie mit: base64 ist eine Art, Bytes als Text anzukleiden, kein Schloss, und Dekodieren ist die Richtung, in der die Daten kleiner werden (zurück auf drei Viertel der kodierten Größe), was genau das Gegenteil von dem ist, wofür Ihre Datenspalte dimensioniert wurde. Die eigentliche Geschichte ist, dass SQL eine Familie von Dialekten ist, und jedes Mitglied nennt seinen Dekodierer mit einem anderen Namen und reagiert auf schlechte Eingabe mit einem völlig anderen Temperament. Dieser Artikel ist der Rundgang.
Die Dekodierer-Aufstellung
Hier ist, wer im Dienst ist, und wie sich jeder verhält, wenn die Eingabe Müll ist. Die Spalte "Wenn es scheitert" kommt darauf an, denn so kommen fehlende Avatars ins Feld: wenn ein Dekodierer in Staging laut scheitert und in der Produktion still.
| Dialekt | Der Aufruf | Was zurückkommt | Wenn es scheitert | Seit wann |
|---|---|---|---|---|
| MySQL 8.x / MariaDB 10.x | FROM_BASE64(str) |
binärer String | stilles NULL |
MySQL 5.6 (2013) |
| PostgreSQL | decode(str, 'base64') |
bytea |
lautes ERROR mit einem Hinweis |
7.2 (2002) |
| SQLite (CLI 3.41+) | base64(str) |
BLOB |
überspringt, was es nicht lesen kann | 3.41.0 (2023) |
| DuckDB | from_base64(str) |
BLOB |
Konvertierungsfehler | aktuelle Releases |
| ClickHouse 18.16+ | base64Decode(str) |
String |
Exception (INCORRECT_DATA) |
18.16.0 (2018) |
| SQL Server 2025+ | BASE64_DECODE(str) |
varbinary |
Msg 9803, drei Zustände | 2025 |
| Oracle | UTL_ENCODE.BASE64_DECODE(raw) |
RAW |
PL/SQL-Exception | 9i-Ära |
| Snowflake | BASE64_DECODE_BINARY(str) |
BINARY |
Fehler, oder NULL mit der TRY_-Variante |
aktuelle Releases |
Beachten Sie die Form der Tabelle: Der Funktionsname ist nie der schwierige Teil. Der schwierige Teil ist die Spalte "Wenn es scheitert", weil diese Spalte entscheidet, ob Ihr Report still Zeilen verliert oder Ihr Batch-Job anhält und um Hilfe bittet.
MySQL und MariaDB: Der Dekodierer, der die Achseln zuckt
Beide Server teilen das Paar TO_BASE64() / FROM_BASE64(). Der Dekodierer nimmt einen String an und gibt einen binären String zurück: eine Abfolge von Bytes ohne angehängten Zeichensatz. Ein NULL als Eingabe gibt ein NULL aus, und hier ist die erste Sache, die Sie sich merken sollten: alles andere, was gültiges base64 nicht ist, ist ebenfalls ein NULL, ohne Warnung. Der Dekodierer zuckt die Achseln, und Ihre Abfrage zieht fröhlich weiter.
SELECT FROM_BASE64('aGVsbG8=') AS restored;
SELECT HEX(FROM_BASE64('aGVsbG8=')) AS as_hex;
SELECT CONVERT(FROM_BASE64('aGVsbG8gd29ybGQ=') USING utf8mb4) AS as_text;
Die mittlere Zeile verdient einen Kommentar, weil sie einen klassischen Moment der Verwirrung erklärt. Der mysql-Kommandozeilen-Client gibt binäre Strings standardmäßig in hexadezimaler Notation aus (eine Einstellung namens binary-as-hex), also zeigt ein nacktes SELECT FROM_BASE64('aGVsbG8=') 0x68656C6C6F statt hello. Das ist kein Fehler und keine Korruption; es ist der Client, der bei binären Daten vorsichtig ist. Wenn Sie Buchstaben wollen, konvertieren Sie mit CONVERT(... USING utf8mb4) oder starten Sie den Client mit --binary-as-hex=0; der HEX()-Aufruf in der mittleren Zeile ist die bewusste Version des Hex, das der Client Ihnen standardmäßig zeigt.
Jetzt die Regeln, die der stille Dekodierer anwendet. Nach dem Ignorieren von Leerzeichen müssen die verbleibenden Zeichen ein Vielfaches von vier bilden, jedes Zeichen muss aus dem Standard-Alphabet stammen (Buchstaben, Ziffern, +, / und =), und Padding darf nur ganz am Ende erscheinen:
SELECT FROM_BASE64('aGVsbG8gd29ybGQ=') AS ok;
SELECT FROM_BASE64('aGVsbG8gd29ybGQ') AS missing_padding;
SELECT FROM_BASE64('!!!') AS nonsense;
Alle drei Zeilen laufen ohne Beschwerde, und die Zeilen zwei und drei geben NULL zurück. Fehlendes Padding, falsche Länge, außerirdische Zeichen: dasselbe Achselzucken. Leerzeichen sind die einzige Nachsicht; Zeilenumbrüche, Wagenrückläufe, Tabs und Leerzeichen werden alle ignoriert, was eine Erlösung ist für alles, was vorher erst durch eine E-Mail ging. Das URL-sichere Alphabet bekommt dagegen das Achselzucken zurück: Ein Unterstrich steht nicht in der Standardtabelle, also ist FROM_BASE64('yv7K_g==') ein NULL, auch wenn die Länge ein sauberes Vielfaches von vier ist. Sie müssen das Alphabet selbst übersetzen, bevor Sie aufrufen, und der Abschnitt URL-sicheres base64 weiter unten zeigt, wie.
Ein weiteres Merkmal, das es zu wissen lohnt: Der Dekodierer und der Kodierer sind ein abgestimmtes Paar. Der Kodierer bricht seine Ausgabe in Zeilen von 76 Zeichen um, und der Dekodierer frisst diese Zeilenumbrüche zum Frühstück. Wenn eine Spalte von TO_BASE64() in dieser selben Datenbankfamilie gefüllt wurde, ist Dekodieren eine perfekte Rundreise. Wenn sie von etwas anderem gefüllt wurde, lesen Sie weiter.
PostgreSQL: Der Dekodierer, der die Stimme erhebt
PostgreSQL trägt base64 seit mindestens Version 7.2, damals 2002, im Kern, was es zur ältesten base64-Maschinerie dieser Familie mit knapper Marge macht. Der Aufruf ist decode(string, 'base64'), und das Ergebnis ist bytea, der native Binär-Typ der Datenbank. Der Begleiter encode(bytea, 'base64') geht die andere Richtung und wird hier nur erwähnt, weil die beiden einen gemeinsamen Formatierungsvertrag teilen: den RFC-2045-Stil, mit Zeilen, die bei 76 Zeichen umgebrochen werden. Der Dekodierer ignoriert seinerseits Wagenrückläufe, Zeilenumbrüche, Leerzeichen und Tabs überall in der Eingabe.
SELECT decode('aGVsbG8gd29ybGQ=', 'base64') AS bytes;
SELECT length(decode('aGVsbG8gd29ybGQ=', 'base64')) AS byte_count;
SELECT convert_from(decode('aMOpbGxv', 'base64'), 'UTF8') AS text;
Die dritte Zeile ist die, nach der Sie ständig greifen werden: convert_from() wandelt das bytea in Text in einer benannten Kodierung um, und es ist der Zeichensatz-Schritt, den die binären Daten brauchen (mehr dazu später im eigenen Abschnitt). aMOpbGxv kommt zurück als héllo, Akzentzeichen inklusive.
Wo sich PostgreSQL vom Rest abhebt, ist die Spalte "Wenn es scheitert". Ungültige Eingabe ist ein harter Fehler, und die Fehlermeldung sagt Ihnen genau, welche Regel gebrochen wurde:
- ein Zeichen außerhalb des Alphabets:
ERROR: invalid symbol "!" found while decoding base64 sequence - ein Padding-Zeichen in der Mitte des Strings:
ERROR: unexpected "=" while decoding base64 sequence - abgeschnittene Eingabe oder fehlendes Padding:
ERROR: invalid base64 end sequence, mit dem Hinweis Input data is missing padding, is truncated, or is otherwise corrupted. - ein URL-sicherer Unterstrich:
ERROR: invalid symbol "_" found while decoding base64 sequence
Für einen Datenbereinigungs-Job ist diese Stimme ein Feature. Die Abfrage scheitert, Sie sehen die Zeile, Sie beheben die Quelle. Der Preis ist, dass eine einzige vergiftete Zeile unter einer Million den ganzen Batch stoppt, also vorfiltern die Leute in Produktionspipelines oft mit einem Regex, bevor sie decode() aufrufen. Und ein kleiner Hinweis zur Anzeige: psql gibt bytea als hex mit \x-Präfix aus, also ist \x68656c6c6f dasselbe "hello", das der MySQL-Client als 0x68656C6C6F zeigt. Zwei Dialekte, zwei Hex-Dialekte.
SQL Server: Der Spätankömmling
Das hier ist die Überraschung der ganzen Familie. SQL Server hat BASE64_DECODE() in Version 2025 ausgeliefert, allgemein verfügbar im November 2025. Davor hatte die populärste Datenbank im Enterprise-Umfeld 36 Jahre lang keinen eingebauten base64-Dekodierer, und die Folklore war voller Workarounds. Die moderne Funktion ist eine saubere: Sie nimmt einen varchar(n)- oder varchar(max)-Ausdruck an und gibt ein varbinary zurück (ein varchar(n)-Ausdruck wird zu varbinary(8000) gemappt, und ein varchar(max)-Ausdruck zu varbinary(max)), wobei NULL einfach hindurchgeht.
SELECT BASE64_DECODE('aGVsbG8gd29ybGQ=') AS bytes;
SELECT CONVERT(VARCHAR(100), BASE64_DECODE('aGVsbG8gd29ybGQ=')) AS text;
SELECT BASE64_DECODE('yv7K_g') AS url_safe_also_works;
Die dritte Zeile ist ein wirklich netter Touch: Der Dekodierer akzeptiert beide RFC-4648-Alphabete, das Standard-Alphabet mit + und / und das URL-sichere mit - und _, und Padding ist optional. Er ignoriert auch die vier Whitespace-Zeichen (Zeilenumbruch, Wagenrücklauf, Tab, Leerzeichen). Wenn er doch scheitert, ist der Fehler Msg 9803, Level 16 mit dem Text Invalid data for type "Base64Decode", und der State-Wert sagt Ihnen, welche Regel Sie getroffen haben: State 20 für ein Zeichen, das in keinem der Alphabete steht, State 21 für Zeichen, die alle gültig sind, aber in einer Form angeordnet sind, die base64 nicht bilden kann, und State 23 für Padding, das zu oft oder zu früh erscheint.
Wenn Sie auf einer Vor-2025-Version stecken, leiht sich der klassische Workaround den XML-Typ, der base64 seit den XML-Schema-Tagen versteht:
SELECT CAST(N'' AS XML)
.value('xs:base64Binary("aGVsbG8=")', 'VARBINARY(MAX)') AS legacy;
Die XML-Maschine dekodiert die Konstante von base64 und gibt die Bytes zurück. Es funktioniert, und es ist das, was eine Generation von SQL-Server-Entwicklern benutzt hat. Es hat auch Kanten: Der base64Binary-Typ ist streng bei der Form, also kann ein String, der in MIME-Zeilen umgebrochen ist und Zeilenumbrüche enthält, nicht geparst werden, und Sie zahlen den Preis der XML-Maschinerie für einen Job, den eine einzelne Funktion jetzt nativ erledigt. Behalten Sie ihn als das Museumsexponat, das er geworden ist.
SQLite: Der Dialekt ohne Dekodierer
SQLite ist der Einzelgänger, und wer versteht, warum, weiß auch, wie man es benutzt. Die Kernbibliothek ist eine kleine, einbettbare Engine, und base64 steht nicht in ihrer Standard-Funktionsliste. Wenn eine Spalte base64 hält, muss der Dekodierer aus einem von vier Orten kommen: der Kommandozeilen-Shell, einer ladbaren Extension, einer benutzerdefinierten Funktion, die von der Host-Anwendung registriert wurde, oder reinem SQL. Hier ist jeder davon.
Die CLI. Ab Version 3.41.0 (Februar 2023) liefert die sqlite3-Kommandozeilen-Shell eine base64()-Funktion mit. Sie dekodiert ein Text-Argument in einen BLOB, was sie perfekt für exploratorische Arbeit direkt aus einem Terminal macht:
$ sqlite3 app.db "SELECT hex(base64('aGVsbG8gd29ybGQ='));"
68656C6C6F20776F726C64
Zwei Temperamente, die man kennen sollte. Erstens ist sie nachsichtig: Zeichen, die sie nicht erkennt, werden übersprungen statt gemeldet, also gibt base64('!!!') einen leeren BLOB zurück statt eines Fehlers. Prima für Neugier, gefährlich für Audits, weil "leer" und "fehlt" in der Ausgabe gleich aussehen. Zweitens ist die Funktion ein Verwandlungskünstler; ein BLOB-Argument wird kodiert in Text (in Zeilen von 72 Zeichen), während ein Text-Argument dekodiert wird in einen BLOB. Derselbe Name, zwei Jobs, gewählt nach dem Typ des Arguments. Kein anderer Dekodierer in dieser Familie macht das, also lesen Sie Ihren Eingangstyp doppelt.
Reines SQL. Die Kernbibliothek hat kein base64, aber sie hat rekursive CTEs, Arithmetik und (seit 3.41.0) unhex(), was ausreicht, um einen echten Dekodierer in ein paar Dutzend Zeilen zu bauen. Das Rezept: eine Alphabettabelle mit 64 Zeilen, die Eingabe in Chunks von vier Zeichen aufgeschnitten, jedes Chunk in eine 24-Bit-Zahl verwandelt, diese Zahl in drei Bytes aufgeteilt, und die Bytes als hex gesammelt, bevor unhex() sie in einen BLOB verwandelt. Hier ist er, im Einsatz auf einer Tabellenspalte:
WITH RECURSIVE
b64(c, v) AS (
SELECT 'A', 0 UNION ALL SELECT 'B', 1 UNION ALL SELECT 'C', 2
UNION ALL SELECT 'D', 3 UNION ALL SELECT 'E', 4 UNION ALL SELECT 'F', 5
UNION ALL SELECT 'G', 6 UNION ALL SELECT 'H', 7 UNION ALL SELECT 'I', 8
UNION ALL SELECT 'J', 9 UNION ALL SELECT 'K', 10 UNION ALL SELECT 'L', 11
UNION ALL SELECT 'M', 12 UNION ALL SELECT 'N', 13 UNION ALL SELECT 'O', 14
UNION ALL SELECT 'P', 15 UNION ALL SELECT 'Q', 16 UNION ALL SELECT 'R', 17
UNION ALL SELECT 'S', 18 UNION ALL SELECT 'T', 19 UNION ALL SELECT 'U', 20
UNION ALL SELECT 'V', 21 UNION ALL SELECT 'W', 22 UNION ALL SELECT 'X', 23
UNION ALL SELECT 'Y', 24 UNION ALL SELECT 'Z', 25 UNION ALL SELECT 'a', 26
UNION ALL SELECT 'b', 27 UNION ALL SELECT 'c', 28 UNION ALL SELECT 'd', 29
UNION ALL SELECT 'e', 30 UNION ALL SELECT 'f', 31 UNION ALL SELECT 'g', 32
UNION ALL SELECT 'h', 33 UNION ALL SELECT 'i', 34 UNION ALL SELECT 'j', 35
UNION ALL SELECT 'k', 36 UNION ALL SELECT 'l', 37 UNION ALL SELECT 'm', 38
UNION ALL SELECT 'n', 39 UNION ALL SELECT 'o', 40 UNION ALL SELECT 'p', 41
UNION ALL SELECT 'q', 42 UNION ALL SELECT 'r', 43 UNION ALL SELECT 's', 44
UNION ALL SELECT 't', 45 UNION ALL SELECT 'u', 46 UNION ALL SELECT 'v', 47
UNION ALL SELECT 'w', 48 UNION ALL SELECT 'x', 49 UNION ALL SELECT 'y', 50
UNION ALL SELECT 'z', 51 UNION ALL SELECT '0', 52 UNION ALL SELECT '1', 53
UNION ALL SELECT '2', 54 UNION ALL SELECT '3', 55 UNION ALL SELECT '4', 56
UNION ALL SELECT '5', 57 UNION ALL SELECT '6', 58 UNION ALL SELECT '7', 59
UNION ALL SELECT '8', 60 UNION ALL SELECT '9', 61 UNION ALL SELECT '+', 62
UNION ALL SELECT '/', 63
),
chunks AS (
SELECT name, b64, (LENGTH(b64) + 3) / 4 AS n
FROM payload
),
seq(name, n, i) AS (
SELECT name, n, 1 FROM chunks
UNION ALL
SELECT name, n, i + 1 FROM seq WHERE i < n
),
vals AS (
SELECT s.name, s.i AS chunk_no, s.n,
COALESCE((SELECT v FROM b64 WHERE c = substr(ch.b64, (s.i - 1) * 4 + 1, 1)), -1) AS v1,
COALESCE((SELECT v FROM b64 WHERE c = substr(ch.b64, (s.i - 1) * 4 + 2, 1)), -1) AS v2,
COALESCE((SELECT v FROM b64 WHERE c = substr(ch.b64, (s.i - 1) * 4 + 3, 1)), -1) AS v3,
COALESCE((SELECT v FROM b64 WHERE c = substr(ch.b64, (s.i - 1) * 4 + 4, 1)), -1) AS v4
FROM seq s
JOIN chunks ch ON ch.name = s.name
),
hexes AS (
SELECT name, chunk_no, n,
(CASE WHEN v1 < 0 THEN 0 ELSE v1 END) * 262144 +
(CASE WHEN v2 < 0 THEN 0 ELSE v2 END) * 4096 +
(CASE WHEN v3 < 0 THEN 0 ELSE v3 END) * 64 +
(CASE WHEN v4 < 0 THEN 0 ELSE v4 END) AS v24,
CASE WHEN v2 >= 0 OR v3 >= 0 THEN 1 ELSE 0 END +
CASE WHEN v3 >= 0 OR v4 >= 0 THEN 1 ELSE 0 END +
CASE WHEN v4 >= 0 THEN 1 ELSE 0 END AS n_bytes
FROM vals
),
acc(name, n, i, hx) AS (
SELECT h.name, h.n, 1,
(CASE WHEN h.n_bytes >= 1 THEN printf('%02X', h.v24 / 65536) ELSE '' END) ||
(CASE WHEN h.n_bytes >= 2 THEN printf('%02X', (h.v24 / 256) % 256) ELSE '' END) ||
(CASE WHEN h.n_bytes >= 3 THEN printf('%02X', h.v24 % 256) ELSE '' END)
FROM hexes h
WHERE h.chunk_no = 1
UNION ALL
SELECT a.name, a.n, a.i + 1,
a.hx || (
SELECT (CASE WHEN h.n_bytes >= 1 THEN printf('%02X', h.v24 / 65536) ELSE '' END) ||
(CASE WHEN h.n_bytes >= 2 THEN printf('%02X', (h.v24 / 256) % 256) ELSE '' END) ||
(CASE WHEN h.n_bytes >= 3 THEN printf('%02X', h.v24 % 256) ELSE '' END)
FROM hexes h
WHERE h.name = a.name AND h.chunk_no = a.i + 1
)
FROM acc a
WHERE a.i < a.n
)
SELECT name, unhex(hx) AS restored
FROM acc
WHERE i = n;
Führen Sie es gegen eine Tabelle mit einer b64-Spalte aus, und Sie bekommen einen BLOB pro Zeile, keine Extensions, keinen Anwendungscode. Die Arithmetik ist simples base64 in ganzzahliger Verkleidung: jedes der vier Zeichen steuert sechs Bits bei, die mittleren zwei Zeichen spannen über eine Byte-Grenze, und die unteren zwei Bits des letzten Zeichens werden verworfen. Es ist die langsamste Option auf dieser Seite (ein rekursiver Durchlauf plus eine Suche pro Chunk), also behalten Sie sie für kleine Payloads und einmalige Archäologie. Für eine dauerhaft laufende Anwendung ist die ehrliche Antwort die dritte Option: Registrieren Sie eine einzeilige benutzerdefinierte Funktion aus der Host-Sprache (Pythons sqlite3-Modul erledigt es in zwei Zeilen mit create_function() und dem Standard-base64-Modul) und lassen Sie die Engine sie wie eine native Funktion aufrufen. Die vierte Option, ladbare Extensions wie die sqlean-Familie, existiert auch, aber sie bedeutet, einen anderen Engine-Build zu installieren, was die meisten Teams lieber vermeiden.
DuckDB: Strikt, klein, meinungsstark
DuckDB ist eine analytische Datenbank mit einem echten Binär-Typ, BLOB, und einer ordentlichen Familie von BLOB-Funktionen darum herum. Der Dekodierer ist from_base64(string), und er sitzt neben seinen Freunden to_base64(), hex(), md5() und sha256() auf derselben Referenzseite, wo die meisten DuckDB-Nutzer ihn das erste Mal treffen.
SELECT from_base64('aGVsbG8gd29ybGQ=') AS bytes;
SELECT decode(from_base64('aMOpbGxv')) AS text;
SELECT hex(from_base64('AAEC')) AS padding_optional;
Die dritte Zeile zeigt eine freundlichere Regel, als Sie vielleicht erwarten: Wenn die Länge ein Vielfaches von vier ist, ist fehlendes Padding kein Problem, AAEC dekodiert ganz ordentlich in die Bytes 00 01 02. Die Strenge zeigt sich in dem Moment, in dem die Form falsch ist. DuckDB will eine Länge, die ein Vielfaches von vier ist, Punkt, und der Konvertierungsfehler sagt genau das:
SELECT from_base64('YWJ');
-- Conversion Error: Could not decode string "YWJ" as base64: length must be a multiple of 4
Zwei weitere Meinungen, die man respektieren sollte. Erstens spricht DuckDBs Dekodierer nur das Standard-Alphabet; ein Unterstrich ist kein Zeichen, das er erkennt, also müssen URL-sichere Tokens übersetzt werden, bevor sie ankommen (das Rezept steht im URL-sicheren Abschnitt). Zweitens hat er keinerlei Toleranz für Leerzeichen. Ein MIME-umgebrochener E-Mail-Anhang mit seinen Zeilenumbrüchen bei 76 Zeichen darin wird scheitern, und die Lösung ist ein replace() über Zeilenumbrüche und Wagenrückläufe vor dem Aufruf. Und weil es keine try_-Variante gibt, die den Schlag weicher macht, ist das sanfte Muster eine Vorkontrolle in derselben Abfrage:
SELECT CASE
WHEN b64 ~ '^[A-Za-z0-9+/]*={0,2}$'
AND MOD(LENGTH(b64), 4) = 0
THEN from_base64(b64)
END AS maybe_bytes
FROM attachments;
Erst der Regex, dann der Dekodierer: Die Abfrage gibt NULL für alles zurück, was überhaupt nicht dekodiert werden kann, und der Dekodierer sieht nur je gut geformte Eingabe.
ClickHouse: Der Spalten-Dekodierer
ClickHouse hat keinen separaten Binär-Typ; sein String ist ohne Murren binärsicher, was bedeutet, dass Dekodieren "in einen String" der ganze Job ist und kein Konvertierungsschritt folgt. Die Funktion gibt es seit Version 18.16.0 (2018) unter dem Namen base64Decode(), und sie behält einen MySQL-artigen Alias, FROM_BASE64(), sodass portierte Abfragen nicht umgeschrieben werden müssen.
SELECT base64Decode('aGVsbG8gd29ybGQ=') AS text;
SELECT tryBase64Decode('definitely not base64') AS gentle;
SELECT base64URLDecode('aHR0cHM6Ly9jbGlja2hvdXNlLmNvbQ') AS url;
Die zweite Zeile ist der ClickHouse-Hausstil in Aktion. Die Engine liebt ihr try-Präfix: tryBase64Decode() schluckt das Scheitern und gibt einen leeren String zurück, während schlichtes base64Decode() eine Exception mit dem Code INCORRECT_DATA wirft und einer Nachricht, die den Übeltäter beim Namen nennt. Wählen Sie die schlichte Form, wenn eine schlechte Zeile die Pipeline stoppen sollte, und die try-Form, wenn der Report weiterlaufen soll, und wählen Sie es bewusst, nicht versehentlich.
Zwei Versionshinweise, weil ClickHouse schnell vorankommt. Vor 26.7 wurde Leerzeichen in der Eingabe abgelehnt; ab 26.7 werden Leerzeichen, Tab, Zeilenvorschub, Wagenrücklauf und Formfeed alle ignoriert, was das Verhalten ist, das Sie für alles wollen, was eine E-Mail oder einen Texteditor berührt hat. Und der moderne Dekodierer erwartet ordentliches Padding an seinen 4-Zeichen-Gruppen, also wird ein Token, das seine Gleichheitszeichen auf dem Weg hinein verloren hat, eine Exception sein statt eines Best-Efforts. Wenn eine Abfrage, die 2023 lief, 2026 anfängt zu werfen, schauen Sie auf die Serverversion, bevor Sie die Daten beschuldigen.
Oracle: RAW oder nichts
Oracles base64-Maschinerie lebt im UTL_ENCODE-PL/SQL-Paket, und es hat eine eigene Persönlichkeit: es nimmt RAW an und gibt RAW zurück, nichts anderes. Kein Text hinein, kein Text hinaus. VARCHAR2 ist Zeichendaten mit einem Zeichensatz; RAW sind nackte Bytes; und das Paket verweigert es, etwas anderes vorzutäuschen. Das funktionierende Muster ist also ein dreischichtiger Sandwich: Cast nach Raw, Dekodieren, Cast zurück nach Text:
SELECT UTL_RAW.CAST_TO_VARCHAR2(
UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW('aGVsbG8gd29ybGQ='))
) AS restored
FROM DUAL;
Jeder Schritt verdient seinen Platz. UTL_RAW.CAST_TO_RAW() interpretiert die Bytes des Textes neu als raw (im Datenbank-Zeichensatz, der für eine moderne Installation in der Regel AL32UTF8 ist, also reist Ihre UTF-8-Eingabe unverändert). UTL_ENCODE.BASE64_DECODE() erledigt die eigentliche Arbeit. Und UTL_RAW.CAST_TO_VARCHAR2() interpretiert die Ergebnis-Bytes als Text in genau diesem Datenbank-Zeichensatz. Springen Sie eine Schicht über, und Sie bekommen einen Type-Mismatch-Fehler, was nichts anderes ist, als Oracle bei der Arbeit, explizit zu sein.
Ungültige Eingabe wirft eine PL/SQL-Exception statt eines stillen NULL, also sollte ein Batch-Dekodieren innerhalb eines Exception-Handlers leben, der die Übeltäter-Zeile loggt. Das Paket trägt auch ein ganzes Museum von Geschwister-Dekodierern: MIME-Header-Dekodierung, quoted-printable, uudecode, text encoding, alle aus derselben Ära. Sie werden vor allem das base64-Paar benutzen, aber die Nachbarn erklären, warum das Paket so organisiert ist, wie es ist: Oracle wollte ein Zuhause für "Daten, die ein Transport-Kostüm tragen".
Eine Größenfalle, die Sie vor dem Start kennen sollten. In reinem SQL ist ein RAW-Wert auf 2000 Bytes gedeckelt, also kann ein base64-Wert, der zu mehr als etwa 1500 raw-Bytes dekodiert wird, überhaupt nicht mit einer einzelnen SELECT-Anweisung dekodiert werden. Größere Payloads brauchen eine PL/SQL-Schleife, die den BLOB in Chunks von 2000 (oder weniger) Bytes durchläuft, jedes Stück dekodiert und die Ergebnisse wieder zusammenflickt. Es ist altmodisch, aber es ist die Standard-Antwort von Oracle, und es ist einer der Orte, wo das 1990er-Typensystem der Sprache noch Ihre 2020er-Abfragen formt.
Snowflake: Bringen Sie Ihr eigenes Alphabet mit
Snowflake trennt seinen Binär-Typ (BINARY) von seinen Text-Typen, und es gibt Ihnen den konfigurierbarsten Dekodierer dieser Familie. Das Arbeitstier ist BASE64_DECODE_BINARY(input), das BINARY zurückgibt, und das optionale zweite Argument ist ein kurzer String, der das Alphabet neu definiert:
SELECT BASE64_DECODE_BINARY('aGVsbG8gd29ybGQ=') AS bytes;
SELECT TO_VARCHAR(BASE64_DECODE_BINARY('aMOpbGxv'), 'UTF-8') AS text;
SELECT TO_VARCHAR(BASE64_DECODE_BINARY('aHR0cHM6Ly9jbGlja2hvdXNlLmNvbQ', '-_'), 'UTF-8') AS url_safe;
Lesen Sie dieses Alphabet-Argument sorgfältig, weil es stellenbasiert ist. Bis zu drei Zeichen sind erlaubt: die ersten zwei überschreiben die Alphabet-Positionen 62 und 63 (die Defaults sind + und /), und das dritte überschreibt das Padding-Zeichen (Default =). Um "das URL-sichere Alphabet verwenden" zu sagen, übergeben Sie '-_'. Um "URL-sicheres Alphabet, aber mit % auffüllen" zu sagen, müssen Sie alle drei Zeichen übergeben, '-_%', auch wenn das einzige, was Sie eigentlich ändern wollen, das Padding-Zeichen ist. Lassen Sie Zeichen weg, und Sie behalten die Defaults; Sie können keine Position überspringen und die nächste füllen.
Zwei Begleiter runden das Set ab. BASE64_DECODE_STRING() macht Dekodierung und Text-Konvertierung in einem Aufruf, also können Sie das TO_VARCHAR() überspringen, wenn die Payload Text ist. Und die TRY_-Varianten, TRY_BASE64_DECODE_BINARY() und TRY_BASE64_DECODE_STRING(), geben bei einem schlechten Wert NULL zurück statt eines Fehlers zu werfen, was die Snowflake-Version der try-Form von ClickHouse ist.
Bytes zu Text: Der Zeichensatz-Schritt
Dekodieren übergibt Ihnen Bytes. Wenn die Payload ein Dokument, ein Name, ein JSON-Fragment ist, schulden Sie ihr einen Schritt mehr: eine Interpretation als Text in einem benannten Zeichensatz. Hierher kommt "es wurde dekodiert, sieht aber falsch aus", weil eine Byte-Sequenz nur dann zu Worten wird, wenn Sie sagen, welche Sprache der Bytes Sie lesen. Die Tabelle ist kurz und lohnt sich zum Auswendiglernen:
| Dialekt | Bytes zu Text | Ungültige Sequenzen |
|---|---|---|
| MySQL / MariaDB | CONVERT(bin USING utf8mb4) |
neu interpretiert; Müll rein, Müll raus |
| PostgreSQL | convert_from(bytes, 'UTF8') |
wirft einen Fehler |
| SQL Server | CAST(bin AS VARCHAR) |
verlustbehaftet, abhängig von der Collation |
| Oracle | UTL_RAW.CAST_TO_VARCHAR2(raw) |
im Datenbank-Zeichensatz neu interpretiert |
| DuckDB | decode(blob) |
Konvertierungsfehler |
| ClickHouse | nicht nötig; String ist der Text |
n/a |
| Snowflake | TO_VARCHAR(bin, 'UTF-8') |
wirft einen Fehler |
| SQLite | CAST(blob AS TEXT) |
keine Validierung überhaupt |
Die Spanne ist breit, mit Absicht. PostgreSQL und DuckDB validieren und weigern sich, was Ihren Downstream-Code vor Mojibake schützt. MySQL und Oracle interpretieren still neu, was schnell ist, aber bedeutet, dass die Datenbank Sie nicht vor einer Latin-1-Payload retten kann, die in einer UTF-8-Welt ankommt. SQLite schaut nicht einmal hin, weil in SQLite ein TEXT-Wert nur Bytes mit einem Etikett sind. Die praktische Regel: Entscheiden Sie den Zeichensatz bevor Sie dekodieren, schreiben Sie ihn als Literal in die Abfrage und testen Sie mit einer Payload, die ein nicht-ASCII-Zeichen enthält (das klassische aMOpbGxv für héllo ist ein guter Kanarienvogel, weil er in jedem falschen Zeichensatz anders kaputtgeht). Für wirklich binäre Payloads überspringen Sie diesen Abschnitt komplett und behalten die Bytes als Bytes.
JWTs: Drei Punkte base64 in einer Spalte
JSON Web Tokens sind das häufigste base64, das Sie in einer Datenbank vorfinden werden, weil Authentifizierungs-Events mit ihren Tokens geloggt werden. Ein JWT sind drei durch Punkte getrennte Stücke: ein Header, eine Payload und eine Signatur. Die ersten beiden sind JSON-Objekte, die als base64 gepackt sind, und hier ist die Wendung, die viele überrascht: JWTs verwenden das URL-sichere Alphabet ohne Padding, nicht die standardmäßige gepadete Form. Ein / würde ein neues Pfadsegment starten, wo Tokens oft reisen, ein + würde in einem Query-String als Leerzeichen gelesen, und die Padding-Gleichheitszeichen wären reine Zeremonie, also ist die Spezifikation (RFC 7515 und RFC 7519) zu - und _ gewechselt und hat das Padding gestrichen.
Ein Token in SQL zu dekodieren ist also ein Vier-Schritte-Tanz: an den Punkten aufteilen, die URL-sicheren Zeichen zurück ins Standard-Alphabet tauschen, das Padding wiederherstellen, das JSON dekodieren und parsen. PostgreSQL, mit seinem JSONB-Typ, ist ein komfortabler Ort dafür:
WITH parts AS (
SELECT split_part('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRldiBVc2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.pDDL2Ljz7cK7vo9Ne4sMFMec1JMhasDUWa82vl-fYsw', '.', 1) AS header_b64,
split_part('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRldiBVc2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.pDDL2Ljz7cK7vo9Ne4sMFMec1JMhasDUWa82vl-fYsw', '.', 2) AS payload_b64
)
SELECT convert_from(
decode(replace(replace(payload_b64, '-', '+'), '_', '/')
|| CASE MOD(LENGTH(payload_b64), 4)
WHEN 2 THEN '=='
WHEN 3 THEN '='
ELSE '' END,
'base64'),
'UTF8')::jsonb AS claims
FROM parts;
Das Ergebnis ist ein JSONB-Wert, den Sie wie jede andere Spalte abfragen können, und für das Token oben kommt er zurück als {"iat": 1516239022, "sub": "1234567890", "name": "Dev User"}. Einen einzelnen Claim aus dem Ergebnis zu ziehen ist dann nur noch claims->>'sub' in einer Folge-Abfrage. Die Padding-Wiederherstellung ist der CASE-Ausdruck: Ein base64url-String, dessen Länge zwei unter einem Vielfachen von vier liegt, braucht zwei Gleichheitszeichen, drei unter braucht eines, und ein exaktes Vielfaches braucht keins.
Gehen Sie einen Schritt weiter, und Sie können sogar eine HS256-Signatur in SQL verifizieren, mit PostgreSQLs pgcrypto-Extension für den HMAC (einmalig aktivieren mit CREATE EXTENSION IF NOT EXISTS pgcrypto;, falls sie noch nicht installiert ist). Berechnen Sie die Signatur über header.payload mit dem gemeinsamen Geheimnis neu, formatieren Sie sie auf dieselbe base64url-Art und vergleichen Sie:
WITH parts AS (
SELECT split_part('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRldiBVc2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.pDDL2Ljz7cK7vo9Ne4sMFMec1JMhasDUWa82vl-fYsw', '.', 1) AS header_b64,
split_part('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRldiBVc2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.pDDL2Ljz7cK7vo9Ne4sMFMec1JMhasDUWa82vl-fYsw', '.', 2) AS payload_b64
)
SELECT rtrim(replace(replace(
encode(hmac((header_b64 || '.' || payload_b64)::bytea,
'sql-secret-key'::bytea, 'sha256'), 'base64'),
'+', '-'),
'/', '_'),
'=') = 'pDDL2Ljz7cK7vo9Ne4sMFMec1JMhasDUWa82vl-fYsw' AS valid
FROM parts;
Der hmac()-Aufruf erzeugt die Digest, encode(..., 'base64') packt sie, und die drei String-Operationen formen sie in die URL-sichere Form ohne Padding um, die das Token trägt. Für das Token und das Geheimnis oben ist die Antwort ein fröhliches t. Halten Sie aber die Einschränkungen bei Ihnen: Das funktioniert nur für HMAC-Algorithmen (HS256, HS384, HS512), es legt ein gemeinsames Geheimnis in eine Datenbank-Anweisung, und es ist gebaut für Reporting, Auditing und Debugging. Alles, was tatsächlich den Zugang reguliert, sollte in der Anwendungsebene mit einer echten JWT-Bibliothek verifiziert werden.
Data URLs: Das Bild in einem String
Das Data-URL-Format (RFC 2397) ist des Webs Art, eine Datei in einen Link einzubetten: data:image/png;base64, gefolgt vom base64 der Datei. Browser fügen sie aus der Zwischenablage ein, Single-Page-Apps betten kleine Bilder darin ein, und jeder dieser Flüsse landet irgendwann in einer Datenbank-Spalte als langer Textwert. Das Format ist data:{media type}[;{parameters}][;base64],{data}, und der einzige Teil, der für das Dekodieren zählt, ist alles nach dem ersten Komma, weil dort die base64-Payload beginnt.
SELECT uri,
CAST(FROM_BASE64(SUBSTRING(uri, LOCATE(',', uri) + 1)) AS BINARY) AS png_bytes
FROM uploads
WHERE uri LIKE 'data:image/png;base64,%';
Das ist der ganze Job in MySQL: Finden Sie das Komma, springen Sie darüber, dekodieren Sie, und Sie halten die Bild-Bytes in einem binären Ausdruck, den Sie in einer BLOB-Spalte speichern oder für die Entduplizierung hashen können. Andere Dialekte tauschen die Funktionen (SUBSTR() und INSTR() in den meisten, substring() und position() in anderen), aber die Form ist identisch.
Drei Warnungen. Erstens ist nicht jede Data URL base64; eine Data URL ohne die ;base64-Markierung trägt stattdessen prozent-kodierten Text, und sie in einen base64-Dekodierer zu füttern ist ein Fehler, den der LIKE-Filter oben verhindern soll. Zweitens ist der media type im Präfix eine Behauptung, keine Tatsache; derselbe String kann image/png sagen und ein JPEG enthalten. Wenn der Inhalt zählt, prüfen Sie die magischen Bytes des dekodierten Ergebnisses (PNG beginnt mit 89 50 4E 47, JPEG mit FF D8). Drittens sind Data URLs groß. Ein 4-Megapixel-Foto wird zu einem grob 5,5-Megabyte-String, was ein Gespräch über Spaltengröße und Speicher ist, nicht über String-Funktionen.
URL-sicheres base64: Das Alphabet, das reist
Abschnitt 5 von RFC 4648 definierte ein zweites Alphabet für base64, weil das ursprüngliche zwei Zeichen hat, die in der URL-Syntax Jobs haben. Das Plus-Zeichen ist die Art, wie Query-Parameter Werte hinzufügen, der Schrägstrich ist die Art, wie Pfade getrennt werden, und das Gleichheitszeichen des Paddings wird prozent-kodiert, in dem Moment, in dem es auf einen Query-String trifft. Die URL-sichere Variante tauscht + gegen - und / gegen _ (beide in URLs harmlos) aus, und die JWT-Spezifikation streicht darüber hinaus das Padding komplett. Das Ergebnis reist durch Links, Pfadsegmente, Dateinamen und Fragment-Bezeichner ohne ein einziges Prozentzeichen.
Sie werden es in einer Datenbank vor allem deshalb treffen, weil Tokens und Links gespeichert wurden, nicht weil die Daten dort geboren wurden. Hier ist, wer es nativ handhaben kann und wer das Zwei-Minuten-Handbuch braucht:
| Dialekt | Natives URL-sicheres Dekodieren | Hinweise |
|---|---|---|
| SQL Server 2025+ | BASE64_DECODE() akzeptiert beide Alphabete |
gar keine Übersetzung nötig |
| ClickHouse 24.6+ | base64URLDecode() |
akzeptiert weiterhin auch + und / |
| Snowflake | BASE64_DECODE_BINARY(s, '-_') |
Alphabet als Positional-Argument |
| MySQL / MariaDB | keines | Zeichen übersetzen, bei Fehlschlag NULL erwarten |
| PostgreSQL | keines | Zeichen übersetzen, bei Fehlschlag einen Fehler erwarten |
| Oracle | keines | Zeichen vor dem RAW-Cast übersetzen |
| DuckDB | keines (lehnt den Unterstrich ab) | Zeichen übersetzen, Länge ein Vielfaches von 4 halten |
| SQLite CLI | keines | Zeichen übersetzen; der Dekodierer überspringt, was er nicht kennt |
Das Handbuch sind zwei REPLACE()-Aufrufe plus Padding-Wiederherstellung, und es ist auf jedem Dialekt dasselbe. In PostgreSQL liest es sich so:
SELECT convert_from(
decode(replace(replace('aGVsbG8', '-', '+'), '_', '/')
|| CASE MOD(LENGTH('aGVsbG8'), 4)
WHEN 2 THEN '=='
WHEN 3 THEN '='
ELSE '' END,
'base64'),
'UTF8') AS text;
Tauschen Sie - zurück zu +, _ zurück zu /, fügen Sie das fehlende Padding basierend auf der Länge modulo vier an, und der Standard-Dekodierer übernimmt von dort aus. Die Eingabe aGVsbG8 (die URL-sichere Form ohne Padding von "hello") kommt zurück als das Wort selbst. Die zwei Fehler, die immer wieder passieren, sind die, die der CASE-Ausdruck verhindert: das Padding zu vergessen, was strenge Dekodierer dazu bringt, eine Länge abzulehnen, die kein Vielfaches von vier ist, und die Zeichen-Übersetzung zu überspringen, was einen Dekodierer, der das URL-sichere Alphabet nicht kennt, an dem Unterstrich ersticken lässt. Schreiben Sie die Übersetzung einmal, als wiederverwendbare Funktion in Ihrer Datenbank, und das ganze Problem hört auf, wiederzukommen.
Dateien, Blobs und Große Dinge
Dekodieren ist der Weg, wie Dateien aus Spalten herauskommen, und jeder Dialekt hat eine etwas andere Ausstiegstür. In DuckDB besteht die Rundreise aus zwei Anweisungen, einer, um eine Datei in einen BLOB zu lesen, und einer, um dekodierte Bytes wieder hinaus zu schreiben:
SELECT filename, octet_length(content) AS size
FROM read_blob('/data/uploads/*.png');
Die Leseseite: read_blob() ist eine Tabellenfunktion, die einen Dateinamen, eine Liste von Namen oder ein Glob-Muster annimmt und pro Datei eine filename- und eine content-Spalte zurückgibt. Die Schreibseite ist ihre eigene Anweisung: COPY im BLOB-Format schreibt rohe Bytes, kein Quoting, kein Escaping, genau das, was eine dekodierte Payload will.
COPY (SELECT from_base64(b64) FROM attachments WHERE id = 42)
TO '/data/restored/cat.png' (FORMAT BLOB);
PostgreSQLs Ausstiegstür ist die Large-Object-API. Ein Large Object ist ein serverseitiger binärer Chunk-Speicher, adressiert über einen OID, und lo_export() schreibt eines davon in eine Datei auf dem Datenbankserver. Es erfordert Superuser-Rechte oder das pg_write_server_files-Privileg, und das Ziel muss ein Pfad sein, auf den der Serverprozess schreiben kann, also ist es in der Praxis ein Job für Wartungsskripte statt für Anwendungscode:
SELECT lo_export(12345, '/tmp/attachments/cat.png');
MySQL hat nur den restriktiven SELECT ... INTO DUMPFILE-Notausgang (eine Zeile, serverseitiger Pfad, FILE-Privileg), und SQL Server hat überhaupt keinen Datei-Schreiber in reinem SQL (Schreiben auf die Festplatte ist ein Job für Client oder Agent, über dessen Export-Tooling), was ein faires Design ist: Die Datenbank speichert die Bytes, die Anwendung entscheidet, wohin die Datei gehört. SQLite sitzt am anderen Ende des Spektrums, wo die Anwendung der Host ist und eine BLOB-Spalte in einem Aufruf der Host-Sprache direkt auf die Festplatte geschrieben werden kann.
Dann gibt es die Obergrenzen, die sich mehr unterscheiden, als man es von Datenbanken erwarten würde, die alle so tun, als wären sie gleich:
| Dialekt | Binär-Typ | Praktische Obergrenze |
|---|---|---|
| PostgreSQL | bytea |
1 GB pro Wert |
| MySQL / MariaDB | BLOB-Familie | max_allowed_packet (64 MB Default in MySQL 8) |
| SQL Server | varbinary(max) |
2 GB pro Wert |
| Oracle | RAW / BLOB |
RAW: 2000 Bytes in SQL, BLOB: 4 GB mit PL/SQL-Chunking |
| SQLite | BLOB |
was Datei und Speicher erlauben |
| DuckDB | BLOB |
sehr groß; Speicher und Festplatte entscheiden |
| ClickHouse | String |
die Spaltengröße ist virtuell, Zeilen sind die Einheit |
| Snowflake | BINARY |
standardmäßig 8 MB pro Wert (schlichte BINARY-Spalten); bis zu 64 MB mit explizitem BINARY(N) |
Die MySQL-Zeile verdient eine Geschichte, weil sie die ist, die Leute in der Produktion überrascht. max_allowed_packet deckelt die Größe eines einzelnen Pakets zwischen Client und Server, und ein base64-String ist Teil dieses Pakets. Ein 50-Megabyte-Foto, kodiert nach base64, ist ein etwa 67-Megabyte-String, der größer als der 64-Megabyte-Default ist, und das Ergebnis ist kein Fehler, den Sie in der Abfrage lesen können: Es ist ein abgeschnittener oder NULL-Wert, der wie Datenkorruption aussieht. Wenn Sie große Dateien durch eine MySQL-Spalte bewegen, prüfen Sie dieses Limit vor dem Start, und denken Sie daran, dass die kodierte Form, nicht die rohen Bytes, dagegen angerechnet wird.
E-Mail-Umbrüche und MIME-Zeilen
Jedes base64, das das E-Mail-System überlebt hat, trägt ein Souvenir: Zeilenumbrüche. MIME, der Satz von Standards, der E-Mails binäre Anhänge tragen lässt (RFC 2045, Abschnitt 6.8), bricht base64-Ausgaben bei 76 Zeichen um und beendet die Zeilen mit Wagenrücklauf und Zeilenvorschub. Der Umbruch existiert, weil das alte E-Mail-Netzwerk Zeilen, die länger waren, nicht vertraute, und das Format wurde seither aus Gewohnheit weitergetragen. Also ist ein Anhang, der in einer Datenbank-Spalte gespeichert ist, häufig ein base64-String mit einem Zeilenumbruch alle 76 Zeichen, und die Beziehung Ihres Dekodierers zu diesen Zeilenumbrüchen entscheidet, ob der Job eine Anweisung ist oder zwei.
| Dekodierer | Frisst den Umbruch? | Wenn nicht |
|---|---|---|
MySQL / MariaDB FROM_BASE64() |
ja | - |
PostgreSQL decode() |
ja | - |
SQL Server BASE64_DECODE() |
ja | - |
SQLite CLI base64() |
ja | - |
| ClickHouse 26.7+ | ja | - |
| ClickHouse vor 26.7 | nein | erst Leerzeichen entfernen |
DuckDB from_base64() |
nein | erst Leerzeichen entfernen |
Oracle UTL_ENCODE.BASE64_DECODE() |
nein | Leerzeichen in der PL/SQL-Schicht entfernen |
Der "erst entfernen"-Fix ist ein Ausdruck, und er ist immer sicher, weil Leerzeichen nicht Teil des base64-Alphabets sind: Keine legitime Payload kann ein Leerzeichen, ein Tab oder einen Zeilenumbruch enthalten, also kann das Entfernen keine Informationen zerstören. In PostgreSQL ist das Idiom ein einzelner regexp_replace():
SELECT decode(regexp_replace(attachment_b64, '\s', '', 'g'), 'base64')
FROM email_attachments;
Jedes Whitespace-Zeichen, Zeilenumbrüche inklusive, verschwindet, und der Dekodierer sieht einen sauberen, durchgehenden String. Führen Sie das in DuckDB aus (mit deren replace() über die beiden Zeilenumbruch-Zeichen) oder in einem Vor-26.7-ClickHouse, und der umgebrochene Anhang dekodiert genau wie der nicht umgebrochene.
API-Payloads, Configs und Auth-Header
Treten Sie von den einzelnen Funktionen zurück, und ein Muster erscheint: base64 in einer Datenbank-Spalte ist fast immer eines von drei Dingen. Ein Feld in einem JSON-Dokument (ein Bild, ein Zertifikat, eine Datei, die eine API inline haben wollte). Ein Konfigurationswert (ein Geheimnis oder eine Credential, die ein bestimmtes Tool bevorzugt in base64, weil base64 auf eine Zeile einer YAML-Datei passt ohne Anführungszeichen, ohne Zeilenumbrüche und ohne Backslashes). Oder ein Authentifizierungs-Artefakt (ein Basic-Auth-Header, ein gespeichertes Token, ein Session-Blob). Hier ist jeder mit seiner Dekodier-Form.
JSON-Felder. Das JSON kam als Text an, das Feld ist ein String, und das base64 versteckt sich darin. Extrahieren Sie das Feld mit der JSON-Funktion Ihres Dialekts, und dekodieren Sie dann. In MySQL ist die ganze Kette ein Ausdruck:
SELECT event_id,
CAST(FROM_BASE64(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.image'))) AS BINARY) AS image_bytes
FROM api_events
WHERE JSON_TYPE(JSON_EXTRACT(payload, '$.image')) = 'STRING';
PostgreSQL macht dasselbe mit JSONB, wo das Feld als Text mit dem ->>-Operator herauskommt und decode() übernimmt. Die JSON_TYPE-Wache in der letzten Zeile zählt mehr, als sie aussieht: Sie hält den Dekodierer von Zeilen fern, wo das Feld eine Zahl, ein verschachteltes Objekt oder fehlend ist, und in MySQL würden diese Zeilen sonst ein stilles NULL zu Ihrer Zählung von "wie viele Events ein Bild hatten" beitragen.
Authentifizierungs-Header. Ein Basic-Auth-Header ist der wörtliche String Basic gefolgt vom base64 von username:password. Ihn in SQL zu dekodieren ist ein Substring und ein Split, was genau der Grund ist, warum Leute es tun (meistens um zu auditieren, welche Benutzer welche Endpunkte trafen, nicht um das Passwort zu verifizieren, das die Datenbank nie im Klartext sehen sollte):
SELECT request_id,
SUBSTRING_INDEX(CAST(FROM_BASE64(SUBSTRING(header_value, 7)) AS CHAR), ':', 1) AS username,
SUBSTRING_INDEX(CAST(FROM_BASE64(SUBSTRING(header_value, 7)) AS CHAR), ':', -1) AS secret
FROM http_log
WHERE header_name = 'Authorization'
AND header_value LIKE 'Basic %';
SUBSTRING(header_value, 7) schält das Basic -Präfix ab, der Dekodierer stellt den ursprünglichen Text wieder her, und die zwei SUBSTRING_INDEX()-Aufrufe teilen ihn am Doppelpunkt auf, erster Teil für den Benutzer, letzter Teil für das Geheimnis. In PostgreSQL nutzt dieselbe Abfrage substring() und split_part().
Konfigurationswerte. Die Dekodier-Richtung hier ist der Audit-Job: Jemand hat ein Geheimnis als base64 in einer Config-Tabelle gespeichert (eine Angewohnheit, die von Kubernetes geerbt wurde, wo Geheimniswerte im Ruhezustand base64 sind), und Sie wollen sehen, was tatsächlich drin ist, oder Sie bauen den Export, den eine neue Umgebung konsumieren wird. Die Form ist eine SELECT pro Wert, und der Zeichensatz-Schritt gilt, wenn der Wert Text ist:
SELECT name,
CONVERT(FROM_BASE64(value) USING utf8mb4) AS plaintext
FROM app_config
WHERE name LIKE '%_secret%';
Behandeln Sie dieses Ergebnis mit der Sorgfalt, die es verdient. Sie haben soeben gespeicherte Geheimnisse in sichtbare Abfrage-Ausgabe verwandelt; stellen Sie sicher, dass das Konto, das die Abfrage ausführt, die Rechte hat, die es haben sollte, dass das Ergebnis nicht in ein Log kopiert wird, und dass die base64-in-config-Angewohnheit einen zweiten Blick bekommt. Base64 ist ein Transport, kein Tresor, und eine Audit-Abfrage ist der Moment, in dem das offensichtlich wird.
Die Fallstricke, die beißen
Jeder Fallstrick auf dieser Liste ist einer, der in mindestens einer Codebase einen Nachmittag gekostet hat, und jeder von ihnen ist spezifisch für die Art, wie SQL-Dialekte base64 handhaben, nicht für base64 selbst.
- Das stille NULL. MySQL und MariaDB dekodieren schlechte Eingabe zu
NULLohne Beschwerde. In einem Report, der auf den dekodierten Wert joinen muss, verschwinden diese Zeilen einfach, und der Unterschied zwischen "0 Zeilen" und "0 Zeilen, weil 14 davon vergiftet waren" ist unsichtbar, bis jemand fragt, warum die Zählung nicht aufgeht. Wenn Ihr Dekodierer die stille Art ist, zählen Sie Ihre NULLs absichtlich. - Die Vierer-Regel, ungleich angewendet. Ein String, dessen Länge kein Vielfaches von vier ist, ist kein base64, aber die Dialekte streiten sich, was zu tun ist: PostgreSQL wirft einen Fehler, DuckDB wirft einen Konvertierungsfehler, ClickHouse wirft eine Exception, MySQL gibt
NULLzurück, und die SQLite-CLI dekodiert still, was sie kann. Dieselbe Datendatei erzeugt fünf verschiedene Ergebnisse auf fünf Datenbanken, weshalb "es hat in Postgres funktioniert" kein Test ist. - Die Alphabet-Unstimmigkeit. Ein URL-sicheres Token (JWT, Link, Dateiname), das in einen Standard-Alphabet-Dekodierer gefüttert wird: SQL Server akzeptiert es, ClickHouses
base64URLDecode()akzeptiert es, Snowflake akzeptiert es mit dem richtigen Argument, und jeder andere gibt entwederNULLzurück, wirft einen Fehler oder, im Fall der SQLite-CLI, wirft still den Unterstrich weg und übergibt Ihnen die falschen Bytes. Der falsche-Bytes-Fall ist der eklige, weil das Ergebnis plausibel aussieht. - Der MIME-Umbruch. Umgebrochene Eingabe in einen Dekodierer, der Zeilenumbrüche nicht frisst (DuckDB, Vor-26.7-ClickHouse, Oracle), scheitert, und das Scheitern sieht oft aus wie "die letzten 76 Zeichen sind Müll" statt "hier ist ein Zeilenumbruch drin", weil der Fehler auf das Zeichen nach dem Umbruch zeigt.
- Der Anzeigetrick. Der mysql-Client gibt binär als hex aus, psql gibt bytea als
\x-hex aus, Snowflake gibt BINARY als hex aus, und Oracle gibt RAW als hex aus. Vier Clients, vier Hex-Notationen, und ein sehr menschlicher Fehler: zu schließen, die Daten seien korrupt, nur weil der Bildschirm Zahlen zeigt. Konvertieren Sie immer explizit, bevor Sie das Ergebnis mit Ihren Augen lesen. - Padding am falschen Ort. Ein Gleichheitszeichen ist nur am Ende legal, eines oder zwei davon. Ein String wie
YQ==BQ==sind zwei gültige Gruppen in einem Kostüm, und die strengen Dekodierer lehnen ihn ab, während die nachsichtigen ihn in etwas dekodieren, das niemand verlangt hat. Wenn Sie je Padding in der Mitte eines gespeicherten Werts sehen, ist der Kodierer, der ihn geschrieben hat, kaputt, und die Daten zu beheben ist ein Einmalkonzert. - Die Zeichensatz-Überraschung. Das Dekodieren gelingt, der Text kommt zurück, und die Akzente sind falsch. Die Bytes waren in Ordnung; die Interpretation war es nicht. Das ist das
CONVERT(... USING latin1), das eigentlichutf8mb4hätte sein sollen, dasCAST(bin AS VARCHAR), das unter einer Collation lief, die ungültige Sequenzen schluckt, dasCAST(blob AS TEXT)in SQLite, das nie prüft. Heften Sie den Zeichensatz als Literal in der Abfrage fest und testen Sie mit einem akzentuierten Kanarienvogel. - Die Obergrenzen. Oracles 2000-Byte-RAW-Limit in SQL-Anweisungen, MySQLs
max_allowed_packet, das die kodierte Größe besteuert, PostgreSQLs 1-GB-bytea-Obergrenze, Snowflakes 8-MB-Standard-BINARY-Länge. Jeder einzelne ist dokumentiert, jeder einzelne wird in der Produktion entdeckt, und jeder einzelne ist eine Größenprüfung, die Sie schreiben hätten können, bevor die Daten groß wurden. - Den dekodierten Bytes zu vertrauen. Base64 kann alles tragen, auch einen String voller Anführungszeichen. Dekodieren ist kein Sanitizing. Was auch immer Sie mit dem dekodierten Text tun (ihn vergleichen, loggen, in eine andere Anweisung konkatenieren), braucht weiterhin die üblichen Schutzmaßnahmen, und eine parameterisierte Abfrage bleibt nach einer base64-Rundreise eine parameterisierte Abfrage.
Wie man auf der richtigen Seite bleibt
- Entscheiden Sie zuerst den Typ, nicht zuerst die Funktion. Ist die Payload binär oder Text? Binär geht zu BLOB/bytea/varbinary und bleibt dort. Text geht durch den Zeichensatz-Schritt mit einer expliziten Kodierung. Die Hälfte aller base64-Schmerzen in SQL ist eine binäre Payload, die in eine Text-Spalte geriet (oder umgekehrt), und die jetzt interpretiert wird.
- Validieren Sie, bevor Sie dekodieren, oder dekodieren Sie sanft. Ein Regex über das Alphabet plus eine Längen-modulo-4-Prüfung kostet nichts und verwandelt einen batch-stoppenden Fehler in ein
NULL, das Sie zählen können. Wo der Dialekt eine try-Form anbietet (ClickHousestryBase64Decode, SnowflakesTRY_BASE64_DECODE_BINARY), benutzen Sie sie für Reporting und behalten Sie die strenge Form für Pipelines, die nicht raten dürfen. - Prüfen Sie die Version des Dialekts, nicht nur der Datenbank. ClickHouse 26.7 änderte die Leerzeichen-Behandlung, SQL Server 2025 ist das erste Release mit der Funktion überhaupt, die SQLite-CLI braucht 3.41, und ClickHouses Padding-Erwartungen haben sich im Laufe der Zeit verschärft. "Es ist ClickHouse" ist keine Spezifikation; "es ist ClickHouse 24.8" schon.
- Dokumentieren Sie das Alphabet jeder Spalte. Eine Spalte, die sowohl Standard- als auch URL-sicheres base64 halten kann, ist eine Spalte, die den nächsten Entwickler verwirren wird. Wenn die Daten aus JWTs kommen, sagen Sie es im Schema-Kommentar; wenn sie aus MIME-Anhängen kommen, sagen Sie das auch. Die Dekodierer-Wahl ist eine Eigenschaft der Spalte, nicht der Abfrage.
- Speichern Sie Bytes, kodieren Sie am Rand. Wenn Sie das Schema kontrollieren, schlägt eine BLOB-Spalte plus Kodierung in der API-Schicht eine base64-Text-Spalte für Speicherung, Indexierung und jede zukünftige Abfrage. Base64 in der Spalte ist eine Kompatibilitätssteuer, und Steuern zahlt man am besten einmal, an der Grenze.
- Rundreise mit einem Kanarienvogel. Bevor Sie einem neuen Dekodier-Weg vertrauen, schicken Sie eine bekannte Payload durch Kodieren und Dekodieren in derselben Datenbank und vergleichen Sie. Der Kanarienvogel sollte ein nicht-ASCII-Zeichen enthalten (um den Zeichensatz-Schritt zu üben), eine Länge, die einen Padding-Schwanz übrig lässt (um die Padding-Regeln zu üben), und, für URL-sichere Pfade, irgendwo ein
-oder_(um die Alphabet-Übersetzung zu üben). - Halten Sie Geheimnisse aus dem Abfrage-Text. JWT-Verifikation mit pgcrypto legt ein gemeinsames Geheimnis in die Anweisung; Config-Audits legen Klartext-Geheimnisse in das Ergebnis. Beides sind legitime Jobs, aber sie verdienen ein beschränktes Konto, ein sauberes Log und eine Review, nicht eine Produktions-Verbindungszeichenkette und ein
SELECT * INTO OUTFILE.
Eine kurze Geschichte des Auspackens in SQL
Das base64-Format selbst ist älter als der nützliche Teil des Internets. Es wurde Mitte der 1990er für MIME standardisiert (RFC 2045, Abschnitt 6.8, der RFC 1521 ablöste, die MIME-Message-Body-Spezifikation von 1993, die die Kodierung trug), und der Name ist nur eine Zählung: Das Alphabet hat 64 Zeichen. Die URL-sichere Variante kam 2006 mit RFC 4648, und die JWT-Spezifikation 2015 machte daraus die Variante, die Sie tatsächlich in Token-Spalten sehen. Aber die Datenbanken trafen das Format jede auf ihrem eigenen Zeitplan, und der Zeitplan sagt Ihnen etwas über die Seele jedes Einzelnen.
2002. PostgreSQL 7.2 listet base64 bereits als gleichberechtigtes Format von encode() und decode() - zeitgleich mit Oracles UTL_ENCODE in der 9i-Ära, und der älteste base64-Support dieser Familie mit knapper Marge. Eine Datenbank mit einem echten Binär-Typ und einem Format-Argument kam früh an, weil die Antwort einen enum-Wert entfernt war.
Anfang 2000er. Oracles UTL_ENCODE-Paket erscheint in der 9i-Ära und trägt base64 neben MIME-Header-, quoted-printable- und uudecode-Funktionen. Es ist RAW hinein und RAW hinaus, was sehr Oracle ist, und es hat diese Form ein Vierteljahrhundert lang gehalten.
2013. MySQL 5.6 fügt TO_BASE64() und FROM_BASE64() hinzu, und MariaDB 10.0 trägt beide in den Fork. Das Paar kodiert mit 76-Zeichen-Zeilen und dekodiert mit Leerzeichen-Toleranz, ein abgestimmtes Set, das sich in einem Dutzend Major-Versionen nicht geändert hat.
2018. ClickHouse 18.16 liefert base64Decode() mit seinem MySQL-artigen Alias, weil die spaltenorientierte Welt Workloads importierte, die bereits base64 in ihren Log-Schemas trugen.
2023. SQLite 3.41.0 fügt der Kommandozeilen-Shell base64() und das base85-Geschwister als anwendung-definierte Funktionen hinzu. Die Kernbibliothek, der Form treu, bekommt nichts; die Shell, wo Menschen tatsächlich an SQLite-Datenbanken herumstochern, bekommt das Werkzeug.
2025. SQL Server 2025, allgemein verfügbar im November 2025, fügt T-SQL BASE64_DECODE() und BASE64_ENCODE() nach einer 36-jährigen Abwesenheit hinzu. Die Release-Notes behandeln sie als bescheidene Funktion; die Community behandelt sie als Rettung.
Das Muster ist sauber, einmal man es sieht. Datenbanken mit einem echten Binär-Typ und einem Format-Argument (PostgreSQL, und auf seine Art Oracle) bekamen base64 an dem Tag, an dem der Bedarf offensichtlich war. Der Rest (MySQL, SQL Server) behandelte es als String-Bequemlichkeit und plante es entsprechend. Und die einbettbare Engine (SQLite) betrachtet es immer noch als Job der Host-Anwendung, mit der CLI als freundliche Ausnahme.
Dinge, die Sie lächeln lassen
- SQL Server verbrachte 1989 bis 2025 ohne base64-Dekodierer, und die Antwort der Community war eine XML-Funktion namens
xs:base64Binary()innerhalb einesCAST(N'' AS XML). Eine ganze Generation von Enterprise-Abfragen dekodierte Tokens durch den XML-Parser, weil der XML-Parser base64 seit 2001 verstand und die SQL-Engine es nicht tat. - Die
base64()-Funktion der SQLite-CLI ist der einzige Verwandlungskünstler in dieser Familie: Geben Sie ihm einen BLOB, und er kodiert, geben Sie ihm Text, und er dekodiert. Die Funktion wechselt ihren Job basierend auf dem Typ ihres Arguments, was ein kleiner Akt der SQL-Telepathie ist und eine echte Falle für Unvorsichtige. - PostgreSQLs Kodierer bricht bei 76 Zeichen um, genau wie der MIME-Standard von 1996, nur beendet er die Zeilen mit einem einzelnen Zeilenumbruch statt mit dem Wagenrücklauf und Zeilenumbruch des Standards. Zwanzig Jahre nach der Spezifikation, ein Zeichen weniger. Der Dekodierer ignoriert beides, also ist die Rebellion unsichtbar, es sei denn, Sie diffen die Ausgabe.
- Im
mysql-Client gibtSELECT FROM_BASE64('aGVsbG8=')0x68656C6C6Faus. Nicht, weil die Daten hex sind, und nicht, weil etwas falsch ist, sondern weil der Client entschieden hat, in Ihrem Namen, dass binäre Strings als hex angezeigt werden sollen. Die Einstellung heißtbinary-as-hex, und sie hat Tausende von Entwicklern davon überzeugt, ihr Dekodierer sei kaputt. - Oracles SQL-Ebenen-RAW-Typ ist auf 2000 Bytes gedeckelt, also kann selbst ein 3-Kilobyte-Zertifikat nicht als RAW-Literal in eine SQL-Anweisung kopiert werden. Die Dekodierung muss in PL/SQL passieren, in Chunks, mit einer Schleife. Das Limit stammt aus den 1990ern; die Schleife ist immer noch die empfohlene Antwort.
- Snowflake zeigt
BINARY-Werte als hex in jedem Ergebnis-Satz an, also kommt eine völlig erfolgreiche Dekodierung von "hello" auf Ihrem Bildschirm als68656C6C6Fan. Zwei Dialekte, zwei Hex-Anzeigen, ein identisches Gefühl der Unruhe. - ClickHouse behält den Alias
FROM_BASE64()neben seinem nativenbase64Decode(), eine kleine Höflichkeit gegenüber den MySQL-Flüchtlingen, die mit Abfragen ankamen, die sonst nicht laufen würden. - Die ganze Familie teilt eine stille Tatsache: base64 ist eine 33-Prozent-Steuer auf dem Weg heraus und eine 25-Prozent-Rückerstattung auf dem Weg hinein, und keiner der acht Dekodierer hier wird Ihnen das sagen, ohne gefragt zu werden. Das Format ist ein Kostüm; der Kleiderschrank ist gratis; das Schneidern ist, worum es in diesem Artikel geht.
Weiter geht's
Dieser Artikel hat sich darum gedreht, die Verkleidung abzulegen: die Funktion in jedem Dialekt, ihr Temperament und die Payloads (JWTs, Data URLs, umgebrochene E-Mail, JSON-Felder, Konfigurationswerte, Auth-Header), die sie tragen. Die andere Richtung ist ein eigenes Tier, mit seinem eigenen Satz an Überraschungen: Welche Kodierer ihre Ausgabe bei 76 Zeichen umbrechen und welche nicht, wie man die URL-sichere Form ohne Padding produziert, die Tokens erwarten, die Größenrechnung, die Ihre Spaltenbreite entscheidet, und was SQL Servers 36-jährige Lücke für alle bedeutet, die noch auf einer älteren Version sind. All das, von TO_BASE64() bis BASE64_ENCODE(), wird in dem verwandten Base64-Kodierungs-Artikel für SQL in der Tiefe abgedeckt, der von dieser Seite verlinkt ist. Dekodieren Sie hier, kodieren Sie dort, und die ganze Rundreise passt in einen Nachmittag.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Kodierung in SQL: Ein vollständiger Leitfaden