Base64-Dekodierung in SQL: Ein vollständiger Leitfaden
Öffnen Sie lange genug eine Produktions-Datenbank, und Sie werden die Verkleidung antreffen. Ein Avatar, der in einem JSON-Export als Wand aus Buchstaben ankommt. Ein JWT, das in einer varchar-Spalte neben einer Benutzer-ID parkt. Ein Zertifikat, das jemand als String versenden wollte, weil das Übertragungsformat kein Binärfeld kennt. 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 tröstliche Eigenschaft: Sobald Sie wissen, welchen Dekodierer Ihr Dialekt spricht, schrumpft der ganze Job auf einen einzigen Funktionsaufruf. Das Format selbst wurde auf der Startseite bereits im Detail erklärt (64 druckbare Zeichen, jede Gruppe von vier steht für drei Eingabe-Bytes, bis zu zwei =-Zeichen als Padding in der letzten Gruppe), also überspringt dieser Artikel die Vorlesung. Zwei Dinge zum Mitnehmen: Base64 ist eine Art, Bytes als Text zu verkleiden, kein Schloss, und das 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 Speicherspalte dimensioniert wurde. Die eigentliche Geschichte ist, dass SQL eine Familie von Dialekten ist und jedes Mitglied seinen Dekodierer anders nennt und auf schlechte Eingabe mit einem völlig anderen Temperament reagiert. Dieser Artikel ist der Rundgang.
Das Aufgebot der Dekodierer
Hier ist, wer Dienst hat, und wie sich jeder verhält, wenn die Eingabe Müll ist. Die "wenn es kaputtgeht"-Spalte ist entscheidend, denn ein Dekodierer, der in Staging laut und in Produktion leise versagt, ist genau der Weg, wie fehlende Avatare ins Feld gelangen:
| Dialekt | Der Aufruf | Was zurückkommt | Wann es kaputtgeht | Seit wann |
|---|---|---|---|---|
| MySQL 8.x / MariaDB 10.x | FROM_BASE64(str) |
binäre Zeichenkette | stiller NULL |
MySQL 5.6 (2013) |
| PostgreSQL | decode(str, 'base64') |
bytea |
lauter ERROR mit 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 Versionen |
| 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-Zeit |
| Snowflake | BASE64_DECODE_BINARY(str) |
BINARY |
Fehler, oder NULL mit der TRY_-Variante |
aktuelle Versionen |
Beachten Sie die Form der Tabelle: Der Funktionsname ist nie das Schwere. Das Schwere ist die "wenn es kaputtgeht"-Spalte, denn diese Spalte entscheidet, ob Ihr Report leise Zeilen verliert oder Ihr Batch-Job anhält und um Hilfe bittet.
MySQL und MariaDB: Der Dekodierer, der Achsel zuckt
Beide Server teilen das Paar TO_BASE64() / FROM_BASE64(). Der Dekodierer nimmt einen String entgegen und gibt eine binäre Zeichenkette zurück: eine Folge von Bytes ohne angehängten Zeichensatz. Ein NULL als Eingang gibt ein NULL als Ausgang, und hier ist die erste Sache, die Sie sich merken sollten: alles andere, das kein gültiges Base64 ist, ist ebenfalls ein NULL, ohne Warnung. Der Dekodierer zuckt die Achseln, und Ihre Abfrage fährt zufrieden fort.
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, denn sie erklärt einen klassischen Moment der Verwirrung. Der mysql-Kommandozeilenclient gibt binäre Zeichenketten 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 Bug und keine Beschädigung; 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.
Nun die Regeln, die der schweigsame Dekodierer anwendet. Nach dem Ignorieren von Leerräumen müssen die verbleibenden Zeichen ein Vielfaches von vier bilden, jedes Zeichen muss aus dem Standardalphabet stammen (Buchstaben, Ziffern, +, / und =), und das 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 Murren, und die Zeilen zwei und drei geben NULL zurück. Fehlendes Padding, falsche Länge, fremde Zeichen: dasselbe Achselzucken. Leerräume 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 eine E-Mail durchlaufen hat. Das URL-sichere Alphabet bekommt hingegen das Achselzucken zurück: ein Unterstrich steht nicht in der Standardtabelle, also ist FROM_BASE64('yv7K_g==') ein NULL, selbst wenn die Länge ein sauberes Vielfaches von vier ist. Sie müssen das Alphabet selbst übersetzen, bevor Sie aufrufen, und der URL-sichere Abschnitt unten zeigt wie.
Noch ein Zug, den es zu wissen lohnt: Dekodierer und Kodierer sind ein abgestimmtes Paar. Der Kodierer bricht seine Ausgabe in Zeilen von 76 Zeichen, und der Dekodierer frisst diese Umbrüche zum Frühstück. Wenn eine Spalte von TO_BASE64() in derselben Datenbankfamilie gefüllt wurde, ist das Dekodieren eine perfekte Rundstrecke. 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 in dieser Familie macht, wenn auch nur knapp. Der Aufruf ist decode(string, 'base64'), und das Ergebnis ist bytea, der native Binärtyp der Datenbank. Der Begleiter encode(bytea, 'base64') geht in die andere Richtung und wird hier nur erwähnt, weil beide denselben Formatierungsvertrag teilen: den RFC-2045-Stil, mit Zeilen, die bei 76 Zeichen umgebrochen werden. Der Dekodierer seinerseits ignoriert Wagenrückläufe, Zeilenumbrüche, Leerzeichen und Tabs an jeder Stelle 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, die Sie ständig zur Hand greifen werden: convert_from() macht aus dem bytea Text in einer benannten Kodierung, und sie ist der Zeichensatz-Schritt, den die binären Daten brauchen (mehr dazu später in seinem eigenen Abschnitt). aMOpbGxv kommt als héllo zurück, das Akzentzeichen inklusive.
Wo sich PostgreSQL von den anderen abhebt, ist die "wenn es kaputtgeht"-Spalte. 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 in einer Million den ganzen Batch stoppt, also filtern Leute in Produktionspipelines oft vorher mit einem Regex, bevor sie decode() aufrufen. Und eine kleine Anzeige-Notiz: psql gibt bytea als mit \x vorangestelltes Hex aus, also ist \x68656c6c6f dasselbe "hello", das der MySQL-Client als 0x68656C6C6F zeigt. Zwei Dialekte, zwei Hex-Dialekte.
SQL Server: Der Spätkommende
Hier ist die Überraschung der ganzen Familie. SQL Server hat BASE64_DECODE() in Version 2025 ausgeliefert, allgemein verfügbar ab November 2025. Davor hatte die beliebteste Datenbank im Enterprise-Land sechsunddreißig Jahre lang keinen eingebauten Base64-Dekodierer, und die Folklore wimmelte von Workarounds. Die moderne Funktion ist eine saubere: Sie nimmt einen varchar(n)- oder varchar(max)-Ausdruck entgegen und gibt ein varbinary zurück (ein varchar(n)-Ausdruck wird zu varbinary(8000), und ein varchar(max)-Ausdruck wird zu varbinary(max)), wobei NULL einfach durchgereicht wird.
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 Standardalphabet mit + und / und das URL-sichere mit - und _, und das Padding ist optional. Er ignoriert auch die vier Leerraumzeichen (Zeilenumbruch, Wagenrücklauf, Tab, Leerzeichen). Wenn er doch kaputtgeht, 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 beiden Alphabete steht, State 21 für Zeichen, die alle gültig sind, aber in einer Form angeordnet, 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-Maschinerie dekodiert die Konstante aus 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 in der Form, also wird ein MIME-umhüllter String mit Zeilenumbrüchen darin nicht geparst, und Sie zahlen den Preis der XML-Maschinerie für einen Job, den eine einzige Funktion jetzt nativ erledigt. Behandeln Sie ihn als das Museumsstück, das er geworden ist.
SQLite: Der Dialekt ohne Dekodierer
SQLite ist der Sonderling, und das Verstehen, warum, sagt Ihnen, wie Sie es benutzen. Die Kernbibliothek ist eine kleine, einbettbare Engine, und Base64 steht nicht in ihrer Standardfunktionsliste. Wenn eine Spalte Base64 hält, muss der Dekodierer von einem von vier Orten kommen: die Kommandozeilen-Shell, eine ladbare Erweiterung, eine benutzerdefinierte Funktion, die die Host-Anwendung registriert, oder reines SQL. Hier ist jeder einzelne.
Die CLI. Ab Version 3.41.0 (Februar 2023) liefert die sqlite3-Kommandozeilen-Shell eine base64()-Funktion. Sie dekodiert ein Textargument in einen BLOB, was sie perfekt macht für exploratorische Arbeit direkt aus einem Terminal:
$ sqlite3 app.db "SELECT hex(base64('aGVsbG8gd29ybGQ='));"
68656C6C6F20776F726C64
Zwei Temperamente, die Sie kennen sollten. Erstens ist er nachsichtig: Zeichen, die er nicht erkennt, werden übersprungen statt gemeldet, also gibt base64('!!!') einen leeren BLOB zurück statt eines Fehlers. Toll für die Neugier, gefährlich fürs Auditieren, denn "leer" und "fehlend" sehen in der Ausgabe gleich aus. Zweitens ist die Funktion formverändernd; ein BLOB-Argument wird in Text kodiert (mit 72-Zeichen-Zeilen), während ein Textargument in einen BLOB dekodiert wird. Derselbe Name, zwei Jobs, gewählt nach dem Typ des Arguments. Kein anderer Dekodierer in dieser Familie macht das, also lesen Sie Ihren Eingangstyp zweimal.
Reines SQL. Die Kernbibliothek hat kein Base64, aber sie hat rekursive CTEs, Arithmetik und (seit 3.41.0) unhex(), was genug ist, um einen echten Dekodierer in einigen Dutzend Zeilen zu bauen. Das Rezept: eine Alphabet-Tabelle mit 64 Zeilen, die Eingabe in Vier-Zeichen-Chunks geschnitten, jeder 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 es, anwendbar 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 erhalten einen BLOB pro Zeile, keine Erweiterungen, kein Anwendungscode. Die Arithmetik ist schlichtes Base64 in Ganzzahl-Kleidung: Jedes der vier Zeichen trägt sechs Bits bei, die mittleren beiden Zeichen überstreifen eine Bytegrenze, und die unteren zwei Bits des letzten Zeichens werden verworfen. Es ist die langsamste Option auf dieser Seite (ein rekursiver Durchlauf plus ein Lookup pro Chunk), also behalten Sie es für kleine Payloads und Einmal-Archäologie. Für eine lang 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 aufrufen. Die vierte Option, ladbare Erweiterungen wie die sqlean-Familie, existiert auch, aber sie bedeutet, einen anderen Engine-Build zu installieren, was die meisten Teams lieber vermeiden.
DuckDB: Streng, klein, meinungsvoll
DuckDB ist eine analytische Datenbank mit einem echten Binärtyp, 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 ihm zum ersten Mal begegnen.
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 ohne Weiteres zu den Bytes 00 01 02. Die Strenge zeigt sich im 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 muss. Erstens spricht DuckDBs Dekodierer nur das Standardalphabet; 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 gar keine Toleranz für Leerräume. Ein MIME-umhüllter E-Mail-Anhang mit seinen 76-Zeichen-Zeilenumbrüchen 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 abfängt, ist das sanfte Muster eine Vorabprüfung 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 Regex, dann Dekodierer: Die Abfrage gibt NULL für alles zurück, was nicht dekodiert werden kann, und der Dekodierer sieht ausschließlich wohlgeformte Eingabe.
ClickHouse: Der Spalten-Dekodierer
ClickHouse hat keinen separaten Binärtyp; sein String ist gerne binärsicher, was bedeutet, dass Dekodieren "in einen String" der ganze Job ist und kein Konvertierungsschritt folgt. Die Funktion ist seit Version 18.16.0 (2018) unter dem Namen base64Decode() unterwegs und behält einen MySQL-artigen Alias, FROM_BASE64(), damit 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 den Fehler und gibt einen leeren String zurück, während schlichtes base64Decode() eine Exception mit dem Code INCORRECT_DATA und einer Meldung wirft, die den Übeltäter beim Namen nennt. Wählen Sie die schlichte Form, wenn eine schlechte Zeile die Pipeline stoppen soll, und die try-Form, wenn der Report weiterlaufen soll, und wählen Sie es bewusst, nicht aus Versehen.
Zwei Versionsnotizen, denn ClickHouse bewegt sich schnell. Vor 26.7 wurden Leerräume in der Eingabe abgelehnt; ab 26.7 werden Leerzeichen, Tabs, Zeilenumbrüche, Wagenrückläufe 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 sauberes Padding in seinen Vier-Zeichen-Gruppen, also wird ein Token, das seine Gleichheitszeichen unterwegs verloren hat, eine Exception sein statt eines Best-Attempts. Wenn eine Abfrage, die 2023 lief, 2026 anfängt zu werfen, schauen Sie auf die Server-Version, bevor Sie die Daten beschuldigen.
Oracle: RAW oder nichts
Oracles Base64-Maschinerie lebt im UTL_ENCODE-PL/SQL-Paket, und sie hat eine eigene Persönlichkeit: Sie nimmt RAW entgegen und gibt RAW zurück, nichts anderes. Kein Text hinein, kein Text heraus. VARCHAR2 ist Zeichendaten mit Zeichensatz, RAW sind nackte Bytes, und das Paket weigert sich, anderes vorzutäuschen. Also ist das funktionierende Muster ein dreischichtiges 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() deutet die Bytes des Textes als raw um (im Zeichensatz der Datenbank, der bei einer modernen Installation normalerweise AL32UTF8 ist, also reist Ihre UTF-8-Eingabe unverändert). UTL_ENCODE.BASE64_DECODE() verrichtet die eigentliche Arbeit. Und UTL_RAW.CAST_TO_VARCHAR2() deutet die Ergebnis-Bytes als Text in genau diesem Datenbank-Zeichensatz um. Überspringen Sie eine Schicht, und Sie erhalten einen Typ-Fehler, und das ist Oracles Job, explizit zu sein.
Ungültige Eingabe wirft eine PL/SQL-Exception statt eines stillen NULL, also sollte ein Batch-Dekodieren in einem Exception-Handler leben, der die Übeltäter-Zeile protokolliert. Das Paket trägt auch ein ganzes Museum an Geschwister-Dekodierern: MIME-Header-Dekodierung, quoted-printable, uudecode, Text-Kodierung, alle aus derselben Ära. Sie werden meist 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 in einer Transport-Verkleidung".
Eine Größen-Fallgrube, die man vor dem Start kennen sollte. 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, ü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 abläuft, jedes Stück dekodiert und die Ergebnisse wieder zusammenfügt. Es ist altmodisch, aber es ist die standardmäßige Oracle-Antwort, und es ist einer der Orte, wo das 1990er-Typsystem der Sprache Ihre 2020er-Abfragen noch formt.
Snowflake: Bringen Sie Ihr eigenes Alphabet mit
Snowflake trennt seinen Binärtyp (BINARY) von seinen Texttypen, und es gibt Ihnen den am meisten konfigurierbaren Dekodierer in dieser Familie. Das Arbeitspferd 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, denn es ist positionell. Bis zu drei Zeichen sind erlaubt: Die ersten beiden überschreiben die Alphabetpositionen 62 und 63 (die Standards sind + und /), und das dritte überschreibt das Padding-Zeichen (Standard =). Um "benutze das URL-sichere Alphabet" zu sagen, übergeben Sie '-_'. Um "URL-sicheres Alphabet, aber mit % paddingen" 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 Standards; Sie können keine Position überspringen und die nächste füllen.
Zwei Begleiter vollenden das Set. BASE64_DECODE_STRING() erledigt Dekodierung und Textkonvertierung 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 NULL für einen schlechten Wert zurück, statt einen Fehler zu werfen, was die Snowflake-Version der ClickHouse-try-Form ist.
Bytes zu Text: Der Zeichensatz-Schritt
Dekodieren gibt Ihnen Bytes. Wenn die Payload ein Dokument, ein Name, ein JSON-Fragment ist, schulden Sie ihr noch einen Schritt: eine Interpretation als Text in einem benannten Zeichensatz. Hierher kommt "es dekodiert, aber sieht falsch aus", denn eine Byte-Folge wird erst zu Worten, wenn Sie sagen, welche Sprache der Bytes Sie lesen. Die Tabelle ist kurz und lohnt sich, sie zu merken:
| Dialekt | Bytes zu Text | Ungültige Folgen |
|---|---|---|
| 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 Kollation |
| Oracle | UTL_RAW.CAST_TO_VARCHAR2(raw) |
im Zeichensatz der Datenbank neu interpretiert |
| DuckDB | decode(blob) |
Konvertierungsfehler |
| ClickHouse | keiner nötig; String ist der Text |
k. A. |
| Snowflake | TO_VARCHAR(bin, 'UTF-8') |
wirft einen Fehler |
| SQLite | CAST(blob AS TEXT) |
keinerlei Validierung |
Die Streuung ist bewusst groß. PostgreSQL und DuckDB validieren und verweigern, was Ihren nachgeschalteten 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, denn in SQLite ist ein TEXT-Wert nur Bytes mit einem Etikett. Die praktische Regel: Entscheiden Sie den Zeichensatz vor dem 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, denn es geht in jedem falschen Zeichensatz anders kaputt). Für wirklich binäre Payloads überspringen Sie diesen Abschnitt komplett und lassen Sie die Bytes als Bytes.
JWTs: Drei Punkte Base64 in einer Spalte
JSON Web Tokens sind das häufigste Base64, das Sie in einer Datenbank herumliegen finden, denn Authentifizierungsereignisse werden zusammen mit ihren Tokens protokolliert. Ein JWT ist drei durch Punkte getrennte Stücke: ein Header, eine Payload und eine Signatur. Die ersten zwei sind JSON-Objekte, die als Base64 gepackt sind, und hier ist die Wendung, die Leute aus dem Konzept bringt: JWTs benutzen das URL-sichere Alphabet ohne Padding, nicht die standardmäßig gepaddete Form. Ein / würde ein neues Pfadsegment beginnen, 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 fallen lassen.
Das Dekodieren eines Tokens in SQL ist daher ein Vier-Schritte-Tanz: An den Punkten teilen, die URL-sicheren Zeichen zurück ins Standardalphabet tauschen, das Padding wiederherstellen, dekodieren und das JSON parsen. PostgreSQL, mit seinem JSONB-Typ, ist ein angenehmer 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 es als {"iat": 1516239022, "sub": "1234567890", "name": "Dev User"} zurück. Einen einzelnen Claim aus dem Ergebnis herauszuholen ist dann einfach claims->>'sub' in einer Folgeabfrage. Die Padding-Wiederherstellung ist der CASE-Ausdruck: Ein base64url-String, dessen Länge zwei Zeichen unter einem Vielfachen von vier liegt, braucht zwei Gleichheitszeichen, drei Zeichen 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 der PostgreSQL-Erweiterung pgcrypto für den HMAC (aktivieren Sie sie einmal mit CREATE EXTENSION IF NOT EXISTS pgcrypto;, falls sie nicht schon 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 den Digest, encode(..., 'base64') packt sie, und die drei Zeichenketten-Operationen formen sie in die URL-sichere Form ohne Padding, die das Token trägt. Für das Token und das Geheimnis oben ist die Antwort ein fröhliches t. Halten Sie aber die Warnungen im Hinterkopf: Das funktioniert nur für HMAC-Algorithmen (HS256, HS384, HS512), es legt ein gemeinsames Geheimnis in eine Datenbank-Anweisung, und es ist für Reporting, Auditierung und Debugging gebaut. Alles, was tatsächlich den Zugang kontrolliert, sollte in der Anwendungsschicht mit einer echten JWT-Bibliothek verifiziert werden.
Data URLs: Das Bild in einem String
Das Data-URL-Format (RFC 2397) ist die Art des Webs, eine Datei in einen Link einzubetten: data:image/png;base64, gefolgt vom Base64 der Datei. Browser kleben sie aus der Zwischenablage, 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ürs Dekodieren zählt, ist alles nach dem ersten Komma, denn dort beginnt die Base64-Payload.
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: Das Komma finden, darüber hinweg springen, dekodieren, und Sie halten die Bild-Bytes in einem binären Ausdruck, den Sie in einer BLOB-Spalte speichern oder für die Deduplizierung hashen können. Andere Dialekte tauschen die Funktionen aus (in den meisten SUBSTR() und INSTR(), in anderen substring() und position()), aber die Form ist identisch.
Drei Warnungen. Erstens ist nicht jede Data URL Base64; eine Data URL ohne den ;base64-Marker trägt stattdessen prozent-kodierten Text, und sie einem Base64-Dekodierer zu füttern ist ein Fehler, den der LIKE-Filter oben verhindern soll. Zweitens ist der Medientyp 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 String von rund 5,5 Megabyte, und das ist eine Spaltengrößen- und Speicherunterhaltung, keine Zeichenketten-Funktions-Unterhaltung.
URL-sicheres Base64: Das reisende Alphabet
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 Pluszeichen 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), und die JWT-Spezifikation lässt darüber hinaus das Padding ganz fallen. Das Ergebnis reist durch Links, Pfadsegmente, Dateinamen und Fragment-Identifikatoren ohne ein einziges Prozentzeichen.
Sie begegnen ihr in einer Datenbank meistens, weil Tokens und Links gespeichert wurden, nicht, weil die Daten dort geboren wurden. Hier ist, wer sie nativ handhaben kann und wer das Zwei-Minuten-Handbuch braucht:
| Dialekt | Natives URL-sicheres Dekodieren | Anmerkungen |
|---|---|---|
| SQL Server 2025+ | BASE64_DECODE() akzeptiert beide Alphabete |
keine Übersetzung überhaupt nötig |
| ClickHouse 24.6+ | base64URLDecode() |
akzeptiert trotzdem auch + und / |
| Snowflake | BASE64_DECODE_BINARY(s, '-_') |
Alphabet als Positionalargument |
| MySQL / MariaDB | keine | Zeichen übersetzen, bei Fehlschlag NULL erwarten |
| PostgreSQL | keine | Zeichen übersetzen, bei Fehlschlag einen Fehler erwarten |
| Oracle | keine | Zeichen vor dem RAW-Cast übersetzen |
| DuckDB | keine (lehnt den Unterstrich ab) | Zeichen übersetzen, Länge als Vielfaches von 4 behalten |
| SQLite CLI | keine | Zeichen übersetzen; der Dekodierer überspringt, was er nicht kennt |
Das Handbuch ist zwei REPLACE()-Aufrufe plus Padding-Wiederherstellung, und es ist in 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 /, hängen Sie das fehlende Padding basierend auf der Länge modulo vier an, und der Standard-Dekodierer übernimmt von dort. Die Eingabe aGVsbG8 (die URL-sichere Form von "hello" ohne Padding) kommt als das Wort selbst zurück. Die zwei Fehler, die immer wieder passieren, sind die, die der CASE-Ausdruck verhindert: das Vergessen des Paddings, was strenge Dekodierer dazu bringt, eine Länge abzulehnen, die kein Vielfaches von vier ist, und das Überspringen der Zeichenübersetzung, was einen Dekodierer, der das URL-sichere Alphabet nicht kennt, auf dem Unterstrich ersticken lässt. Schreiben Sie die Übersetzung einmal, als wiederverwendbare Funktion in Ihrer Datenbank, und das ganze Problem hört auf, wiederzukehren.
Dateien, Blobs und große Dinge
Dekodieren ist der Weg, wie Dateien aus Spalten herauskommen, und jeder Dialekt hat eine etwas andere Ausgangstür. In DuckDB ist die Rundstrecke zwei Anweisungen, eine, um eine Datei in einen BLOB zu lesen, und eine, um dekodierte Bytes wieder rauszuschreiben:
SELECT filename, octet_length(content) AS size
FROM read_blob('/data/uploads/*.png');
Die Lese-Seite: 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 Schreib-Seite 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 Ausgangstür ist die Large-Object-API. Ein Large Object ist ein serverseitiger binärer Chunk-Speicher, adressiert per OID, und lo_export() schreibt eines davon in eine Datei auf dem Datenbankserver. Es benötigt Superuser-Rechte oder die pg_write_server_files-Berechtigung, 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 Anwendungscode:
SELECT lo_export(12345, '/tmp/attachments/cat.png');
MySQL hat nur den restriktiven SELECT ... INTO DUMPFILE-Notausgang (eine Zeile, serverseitiger Pfad, FILE-Berechtigung), und SQL Server hat gar keinen Dateischreiber in reinem SQL (das Schreiben auf die Platte ist ein Client- oder Agent-Job, über sein Export-Tooling), was ein faires Design ist: Die Datenbank speichert die Bytes, die Anwendung entscheidet, wo die Datei hingehö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 Platte geschrieben werden kann.
Dann gibt es die Decken, die sich mehr unterscheiden, als man von Datenbanken erwarten würde, die alle so tun, als wären sie gleich:
| Dialekt | Binärtyp | Praktische Decke |
|---|---|---|
| PostgreSQL | bytea |
1 GB pro Wert |
| MySQL / MariaDB | BLOB-Familie | max_allowed_packet (64 MB Standard 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 Platte entscheiden |
| ClickHouse | String |
Spaltengröße ist virtuell, Zeilen sind die Einheit |
| Snowflake | BINARY |
8 MB pro Wert standardmäßig (schlichte BINARY-Spalten); bis zu 64 MB mit explizitem BINARY(N) |
Die MySQL-Zeile verdient eine Geschichte, denn sie ist die, die Leute in 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 String von etwa 67 Megabyte, was größer ist als der 64-Megabyte-Standard, und das Ergebnis ist kein Fehler, den man in der Abfrage lesen kann: Es ist ein abgeschnittener oder NULL-Wert, der wie Datenkorruption aussieht. Wenn Sie große Dateien durch eine MySQL-Spalte bewegen, prüfen Sie diese Grenze vor dem Start, und denken Sie daran, dass die kodierte Form, nicht die rohen Bytes, dagegen zählt.
E-Mail-Umbrüche und MIME-Zeilen
Jedes Base64, das das E-Mail-System überlebt hat, trägt ein Andenken: Zeilenumbrüche. MIME, der Satz an Standards, der E-Mails binäre Anhänge tragen lässt (RFC 2045, Abschnitt 6.8), bricht die Base64-Ausgabe bei 76 Zeichen um und beendet die Zeilen mit einem Wagenrücklauf und einem Zeilenumbruch. Der Umbruch existiert, weil das alte E-Mail-Netzwerk Zeilen länger als das nicht traute, 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 | Leerräume zuerst entfernen |
DuckDB from_base64() |
nein | Leerräume zuerst entfernen |
Oracle UTL_ENCODE.BASE64_DECODE() |
nein | Leerräume in der PL/SQL-Schicht entfernen |
Der "zuerst entfernen"-Fix ist ein einzelner Ausdruck, und er ist immer sicher, denn Leerräume sind kein Teil des Base64-Alphabets: Keine legitime Payload kann ein Leerzeichen, Tab oder einen Zeilenumbruch enthalten, also kann ihr Entfernen keine Information zerstören. In PostgreSQL ist das Idiom ein einzelnes regexp_replace():
SELECT decode(regexp_replace(attachment_b64, '\s', '', 'g'), 'base64')
FROM email_attachments;
Jedes Leerraumzeichen, Zeilenumbrüche inklusive, geht, und der Dekodierer sieht einen sauberen, zusammenhängenden String. Führen Sie das in DuckDB (mit seinem replace() über die beiden Umbruch-Zeichen) oder in einem Vor-26.7-ClickHouse aus, 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 einbetten wollte). Ein Konfigurationswert (ein Geheimnis oder eine Zugangsangabe, die ein Tool lieber als Base64 mag, weil Base64 auf einer 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 jedes 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. In MySQL ist die ganze Kette ein einzelner 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-Wacht auf der letzten Zeile zählt mehr, als sie aussieht: Sie hält den Dekodierer von Zeilen fern, in denen 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. Das Dekodieren in SQL ist ein Substring und ein Teilen, und genau deshalb machen Leute es (meistens um zu auditieren, welche Benutzer welche Endpunkte getroffen haben, nicht um das Passwort zu verifizieren, das die Datenbank niemals 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) pult das Basic -Präfix ab, der Dekodierer stellt den Originaltext wieder her, und die beiden SUBSTRING_INDEX()-Aufrufe teilen es am Doppelpunkt, erster Teil für den Benutzer, letzter Teil für das Geheimnis. In PostgreSQL verwendet 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 Gewohnheit, geerbt von Kubernetes, wo Geheimnis-Werte 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 gerade 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-Gewohnheit einen zweiten Blick bekommt. Base64 ist ein Transport, kein Tresor, und eine Audit-Abfrage ist der Moment, in dem das offensichtlich wird.
Die Fallen, die beißen
Jede Falle auf dieser Liste ist eine, die in mindestens einer Codebase einen Nachmittag gekostet hat, und jede 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 verbindet, verschwinden diese Zeilen einfach, und der Unterschied zwischen "0 Zeilen" und "0 Zeilen, weil 14 von ihnen vergiftet waren" ist unsichtbar, bis jemand fragt, warum die Zählung nicht aufgeht. Wenn Ihr Dekodierer die leise Art ist, zählen Sie Ihre NULLs absichtlich. - Die Vielfaches-von-vier-Regel, ungleichmäßig angewandt. Ein String, dessen Länge kein Vielfaches von vier ist, ist kein Base64, aber die Dialekte sind sich uneinig, was man tun soll: 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, und deshalb ist "es lief in Postgres" kein Test. - Der Alphabete-Mismatch. Ein URL-sicheres Token (JWT, Link, Dateiname), das einem 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, lässt den Unterstrich still fallen und reicht Ihnen die falschen Bytes. Der falsche-Bytes-Fall ist der unangenehme, denn das Ergebnis sieht plausibel aus. - Der MIME-Umbruch. Umgebrochene Eingabe in einen Dekodierer, der keine Zeilenumbrüche frisst (DuckDB, Vor-26.7-ClickHouse, Oracle), scheitert, und der Fehler sieht oft aus wie "die letzten 76 Zeichen sind Müll" statt "da 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, ein sehr menschlicher Fehler, zu schlussfolgern, die Daten seien beschädigt, weil der Bildschirm Zahlen zeigt. Konvertieren Sie immer explizit, bevor Sie das Ergebnis mit Ihren Augen lesen. - Padding am falschen Platz. Ein Gleichheitszeichen ist nur am Ende legal, ein oder zwei von ihnen. Ein String wie
YQ==BQ==ist zwei gültige Gruppen in einer Verkleidung, und die strengen Dekodierer lehnen ihn ab, während die nachsichtigen ihn in etwas dekodieren, das niemand angefordert hat. Wenn Sie je Padding in der Mitte eines gespeicherten Werts sehen, ist der Kodierer, der ihn geschrieben hat, kaputt, und das Beheben der Daten ist ein Einmal-Job. - 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), dasutf8mb4hätte sein sollen, dasCAST(bin AS VARCHAR), das unter einer Kollation lief, die ungültige Folgen schluckt, dasCAST(blob AS TEXT)in SQLite, das nie prüft. Verankern Sie den Zeichensatz als Literal in der Abfrage und testen Sie mit einem akzentuierten Kanarienvogel. - Die Decken. Oracles 2000-Byte-RAW-Limit in SQL-Anweisungen, MySQLs
max_allowed_packet, das die kodierte Größe besteuert, PostgreSQLs 1-GB-Bytea-Decke, Snowflakes 8-MB-Standard-BINARY-Länge. Jede ist dokumentiert, jede wird in Produktion entdeckt, und jede ist eine Größenprüfung, die Sie hätten schreiben können, bevor die Daten groß wurden. - Den dekodierten Bytes zu trauen. Base64 kann alles tragen, auch einen String voller Anführungszeichen. Dekodieren ist keine Sanitisierung. Was auch immer Sie mit dem dekodierten Text tun (vergleichen, loggen, in eine andere Anweisung konkatenieren), braucht weiterhin die üblichen Schutzmaßnahmen, und eine parameterisierte Abfrage bleibt nach einem Base64-Rundweg 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 nach 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 Textspalte gewandert ist (oder umgekehrt) und jetzt interpretiert wird.
- Validieren Sie vor dem Dekodieren, oder dekodieren Sie sanft. Ein Regex über das Alphabet plus eine Längen-Modulo-4-Prüfung kostet nichts und verwandelt einen Batch-stoppernden Fehler in ein
NULL, das Sie zählen können. Wo der Dialekt eine try-Form anbietet (ClickHousestryBase64Decode, SnowflakesTRY_BASE64_DECODE_BINARY), verwenden Sie sie fürs 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 hat die Leerraum-Behandlung geändert, SQL Server 2025 ist die 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 standardmäßiges als auch URL-sicheres Base64 halten kann, ist eine Spalte, die den nächsten Entwickler verunsichern wird. Wenn die Daten von JWTs kommen, sagen Sie es im Schema-Kommentar; wenn sie von MIME-Anhängen kommen, sagen Sie auch das. Die Dekodierer-Wahl ist eine Eigenschaft der Spalte, nicht der Abfrage.
- Speichern Sie Bytes, kodieren Sie an der Kante. Wenn Sie das Schema kontrollieren, schlägt eine BLOB-Spalte plus Kodierung in der API-Schicht eine Base64-Textspalte für Speicherung, für Indizierung und für jede zukünftige Abfrage. Base64 in der Spalte ist eine Kompatibilitätssteuer, und Steuern zahlt man am besten einmal, an der Grenze.
- Rundweg mit einem Kanarienvogel. Bevor Sie einem neuen Dekodier-Pfad vertrauen, schieben 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-Rest lässt (um die Padding-Regeln zu üben), und, für URL-sichere Pfade, ein
-oder_irgendwo (um die Alphabet-Übersetzung zu üben). - Halten Sie Geheimnisse aus dem Abfrage-Text. JWT-Verifizierung mit pgcrypto legt ein gemeinsames Geheimnis in die Anweisung; Config-Audits legen Klartext-Geheimnisse ins Ergebnis. Beide sind legitime Jobs, aber sie verdienen ein eingeschränktes Konto, ein sauberes Log und eine Review, nicht einen Produktions-Verbindungsstring 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 1993er MIME-Nachrichten-Körper-Spezifikation, 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 aus ihr die, die Sie tatsächlich in Token-Spalten sehen. Aber die Datenbanken trafen das Format jeweils nach ihrem eigenen Zeitplan, und der Zeitplan sagt etwas über die Seele jedes einzelnen aus.
2002. PostgreSQL 7.2 listet Base64 bereits als First-Class-Format von encode() und decode() - zeitgenössisch mit Oracles UTL_ENCODE in der 9i-Ära, und die älteste Base64-Unterstützung in dieser Familie mit knapper Marge. Eine Datenbank mit einem echten Binärtyp und einem Format-Argument war früh da, denn die Antwort war ein enum-Wert entfernt.
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 heraus, was sehr Oracle ist, und es hat diese Form seit einem Vierteljahrhundert beibehalten.
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 Leerraum-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 aus, weil die säulenorientierte Welt Workloads importierte, die bereits Base64 in ihren Log-Schemas trugen.
2023. SQLite 3.41.0 fügt base64() und seinen base85-Bruder der Kommandozeilen-Shell als anwendungsdefinierte Funktionen hinzu. Die Kernbibliothek bekommt, ihrer Form treu, nichts; die Shell, wo Menschen tatsächlich in SQLite-Datenbanken herumstochern, bekommt das Werkzeug.
2025. SQL Server 2025, allgemein verfügbar im November 2025, fügt BASE64_DECODE() und BASE64_ENCODE() nach einer sechsunddreißigjährigen Abwesenheit zu T-SQL hinzu. Die Release Notes behandeln sie als bescheidene Funktion; die Community behandelt sie als Rettung.
Das Muster ist sauber, wenn man es sieht. Datenbanken mit einem echten Binärtyp und einem Format-Argument (PostgreSQL und auf seine Art Oracle) bekamen Base64 an dem Tag, an dem der Bedarf offensichtlich war. Die anderen (MySQL, SQL Server) behandelten es als Zeichenketten-Bequemlichkeit und planten es entsprechend. Und die einbettbare Engine (SQLite) hält es immer noch für den 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()in einemCAST(N'' AS XML). Eine ganze Generation von Enterprise-Abfragen hat Tokens über den XML-Parser dekodiert, weil der XML-Parser Base64 seit 2001 verstand und die SQL-Engine es nicht tat. - Die
base64()der SQLite-CLI ist der einzige Formwandler 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 eine kleine Tat der SQL-Telepathie und eine echte Falle für Unvorsichtige ist. - PostgreSQLs Kodierer bricht bei 76 Zeichen um, genau wie der MIME-Standard von 1996, nur dass er die Zeilen mit einem einzelnen Zeilenumbruch beendet statt mit dem Wagenrücklauf und Zeilenumbruch des Standards. Zwanzig Jahre nach der Spezifikation, ein Zeichen weniger. Der Dekodierer ignoriert beide, also ist der Aufstand unsichtbar, wenn Sie die Ausgabe nicht diffen.
- Im
mysql-Client gibtSELECT FROM_BASE64('aGVsbG8=')0x68656C6C6Faus. Nicht, weil die Daten Hex sind, und nicht, weil etwas falsch ist, sondern weil der Client, in Ihrem Namen, entschieden hat, dass binäre Zeichenketten als Hex angezeigt werden sollen. Die Einstellung heißtbinary-as-hex, und sie hat Tausende von Entwicklern davon überzeugt, dass ihr Dekodierer kaputt ist. - Oracles SQL-Ebenen-RAW-Typ ist auf 2000 Bytes gedeckelt, also kann ein 3-Kilobyte-Zertifikat nicht einmal in eine SQL-Anweisung als RAW-Literal eingefügt werden. Das Dekodieren 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 in jedem Ergebnis-Set als Hex an, also kommt ein vollkommen gelungenes Dekodieren von "hello" auf Ihrem Bildschirm als68656C6C6Fan. Zwei Dialekte, zwei Hex-Anzeigen, ein identisches Gefühl des Unbehagens. - 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 raus und eine 25-Prozent-Rückerstattung auf dem Weg rein, und keiner der acht Dekodierer hier wird Ihnen das ohne Nachfrage sagen. Das Format ist ein Kostüm; der Garderobenraum ist kostenlos; das Zuschneidern ist das, worum es in diesem Artikel geht.
Weiter geht's
Dieser Artikel hat sich damit beschäftigt, die Verkleidung auszuziehen: die Funktion in jedem Dialekt, ihr Temperament und die Payloads (JWTs, Data URLs, umgebrochene E-Mails, JSON-Felder, Config-Werte, Auth-Header), die sie tragen. Die andere Richtung ist ein eigenes Tier, mit ihrem eigenen Satz an Überraschungen: welche Kodierer ihre Ausgabe bei 76 Zeichen umbrechen und welche nicht, wie man die URL-sichere Form ohne Padding erzeugt, die Tokens erwarten, die Größenrechnung, die Ihre Spaltenbreite entscheidet, und was SQL Servers 36-jährige Lücke für alle bedeutet, die immer noch auf einer älteren Version sind. All das, von TO_BASE64() bis BASE64_ENCODE(), wird in dem verwandten Base64-Encoding-Artikel für SQL im Detail behandelt, verlinkt von dieser Seite. Dekodieren Sie hier, kodieren Sie dort, und die ganze Rundstrecke passt in einen Nachmittag.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Kodierung in SQL: Ein vollständiger Leitfaden