Кодирование Base64 в SQL: полное руководство
От времени до времени базе данных приходится говорить с внешним миром, а внешний мир не всегда говорит байтами. API хочет ваш логотип внутри JSON-строки. Экспорт конфигурации хочет секрет, который влезает на одну строку YAML без кавычек и обратных слешей. Сервисный скрипт хочет отправить файл через систему, которая несёт только текст. Вот тот самый момент, когда ваши данные надевают костюм из букв, и имя этого костюма - base64.
Сам формат уже разобран на домашней странице (64 печатаемых символа, каждый из четырёх стоит за тремя байтами входа, до двух знаков = заполняют последнюю группу), поэтому лекцию пропустим и пойдём прямо к машинной работе. Две вещи, за которые нужно держаться: кодирование - это направление, в котором данные становятся больше, так что ширины колонок и лимиты пакетов ощущают каждый байт, и энкодеры в этой SQL-семье не сходятся в двух вещах, которые труднее всего отменить потом: какие байты они читают, когда в вашей колонке лежат буквы, и куда они ставят переносы строк в том, что пишут.
Шпаргалка по энкодерам
Кто дежурит, что они едят и где ломают свой вывод. Последние две колонки - те, что кусаются, потому что строка, набитая незапрошенными переносами строк, и строка с другим алфавитом - оба варианта - вполне валидные base64-строки, которые ваш потребитель всё равно отклонит:
| Диалект | Вызов | Тип входа | Оборачивает на 76? | URL-безопасный вариант | С какого времени |
|---|---|---|---|---|---|
| MySQL 8.x / MariaDB 10.x | TO_BASE64(str) |
строка (применяется кодировка) | да | ничего | MySQL 5.6 (2013) |
| PostgreSQL | encode(bytea, 'base64') |
bytea |
да, только LF | ничего | 7.2 (2002) |
| SQLite (CLI 3.41+) | base64(blob) |
BLOB |
да, на 72 | ничего | 3.41.0 (2023) |
| DuckDB | to_base64(blob) |
BLOB |
нет | ничего | современные релизы |
| ClickHouse 18.16+ | base64Encode(x) |
что угодно, приводится к String | нет | base64URLEncode() |
18.16 (2018) |
| SQL Server 2025+ | BASE64_ENCODE(bin [, url_safe]) |
varbinary |
нет | второй аргумент | 2025 |
| Oracle | UTL_ENCODE.BASE64_ENCODE(raw) |
RAW |
нет | ничего | эпоха 9i |
| Snowflake | BASE64_ENCODE(binary) |
BINARY |
нет | ничего | актуальные релизы |
Прочитайте таблицу слева направо - и работа распадается на два решения. Первое: как ваши байты туда попадают: колонка типа входа - место, где рождаются сюрпризы с кодировкой, потому что «один и тот же текст» - это разные байты под разными коллациями. Второе: что выходит на другой стороне: колонка обёртки решает, будет ли ваш результат одной плоской строкой или стихотворением с переносом каждые 76 символов, а URL-безопасная колонка решает, можно ли вообще положить результат в ссылку.
Сначала решите, какие байты вы имеете в виду
Энкодер упаковывает байты, но ваша колонка обычно держит буквы, а буквы - это байты только если вы сказали, какой именно алфавит байтов. TO_BASE64() в MySQL читает свой аргумент в кодировке соединения, что удобно, пока не перестаёт: тот же 'héllo' уезжает разным base64 под клиентом latin1 и клиентом utf8mb4. Когда вы имеете в виду точные байты, как они сохранены, сначала заморозьте их бинарным приведением:
SELECT TO_BASE64('hello') AS from_text;
SELECT TO_BASE64(CAST('héllo' AS BINARY)) AS utf8_bytes;
Вторая строка возвращается как aMOpbGxv, где два шестнадцатеричных байта C3 A9 посередине - способ UTF-8 написать é. PostgreSQL строже на входе: encode() отказывается смотреть на всё, что не bytea, так что текстовое значение сначала должно назвать свою кодировку, а сырые байты могут приехать в виде шестнадцатеричного литерала:
SELECT encode(convert_to('héllo', 'UTF8'), 'base64') AS text_as_utf8;
SELECT encode('\x68656c6c6f'::bytea, 'base64') AS raw_bytes;
У каждого другого диалекта своя передняя дверь к той же идее, и все они сводятся к «возьми байты, потом упаковай их»:
| Диалект | Текст в байты | Вызов энкодера |
|---|---|---|
| T-SQL | CAST('héllo' AS VARBINARY(8000)) через коллацию колонки |
BASE64_ENCODE(bin) |
| Snowflake | TO_BINARY('héllo', 'UTF-8') |
BASE64_ENCODE(binary) |
| Oracle | UTL_RAW.CAST_TO_RAW('héllo') |
UTL_ENCODE.BASE64_ENCODE(raw) |
| DuckDB | encode('héllo') даёт BLOB |
to_base64(blob) |
| SQLite CLI | литерал BLOB, например X'68656C6C6F' |
base64(blob) |
Практическое правило то же, что и на стороне декодирования: решите кодировку до того, как кодировать, запишите её в запрос литералом и прогоните одну пелод с акцентами через весь конвейер, прежде чем доверять колонке. Одно héllo ловит любую неверную коллацию и ничего не стоит.
Потом наблюдайте, что выходит
Как только байты упакованы, энкодеры расходятся из-за переносов строк. Трое из них обёртывают вывод: MySQL и PostgreSQL - на 76 символах, почтовая привычка, а SQLite CLI - на 72; остальные возвращают одну плоскую строку, какой бы длинной она ни стала. Разницу легко не заметить и дорого найти, потому что base64-поле со скрытыми переносами - это поле, которое ломает JSON-парсер на середине:
SELECT LENGTH(TO_BASE64(REPEAT('x', 300))) AS wrapped,
LENGTH(REPLACE(TO_BASE64(REPEAT('x', 300)), '\n', '')) AS flat;
Триста байтов входа возвращаются четырьмястами символами base64, а тот же вызов меряется в 405, потому что пять переносов строк поехали вместе. Арифметика за этим достаточно мала, чтобы держать её в голове: плоская длина - это длина входа, делённая на три и округлённая вверх, умноженная на четыре. Если ваш энкодер обёртывает, прибавьте по одному переносу между строками по 76 символов, то есть плоскую длину, делённую на 76 и округлённую вверх, минус один. Триста байтов: 400 в плоском виде, 405 в обёрнутом. Сто одиннадцать байтов: 148 в плоском, 149 в обёрнутом. Один лишний перенос строки сверх спланированного - так колонка VARCHAR(500) начинает молча обрезать пелод VARCHAR(480).
Два следствия, которые стоит записать. Рассчитывайте размеры текстовых колонок под плоскую длину плюс небольшой запас, если писатель может обёртывать, либо запретите обёртку на стороне писателя и рассчитывайте под плоскую. И помните: лимит, с которым борется ваш результат, - это лимит строки, а не байтов: в MySQL обёрнутый текст засчитывается в max_allowed_packet (по умолчанию 64 MB в MySQL 8), так что фото на 50 мегабайт, закодированное примерно в 67 мегабайт букв, не влезает в пакет по умолчанию, хотя сырой файл влез бы.
URL-безопасный Base64: путешествующий алфавит
Раздел 5 RFC 4648 определил второй алфавит для base64, потому что у исходного два символа имеют дела в URL-синтаксисе. Знак плюса добавляет параметры запроса, слеш разделяет сегменты пути, а знак равенства заполнителя процентно-кодируется в тот миг, как встречается со строкой запроса. URL-безопасный вариант меняет + на - и / на _, а спецификация JWT сверху сбрасывает заполнитель вообще, так что токен может сидеть в ссылке, сегменте пути или имени файла без единого процентного знака.
Только один диалект в этой семье отгружает переключатель нативно. BASE64_ENCODE() в SQL Server 2025 принимает необязательный второй аргумент, и при включённом он результат использует - и _ и пропускает заполнитель:
SELECT BASE64_ENCODE(0xCAFECAFE) AS standard;
SELECT BASE64_ENCODE(0xCAFECAFE, TRUE) AS url_safe;
Одни и те же четыре байта возвращаются как yv7K/g== и yv7K_g. ClickHouse держит варианты как отдельные функции, и его URL-безопасная форма тоже сбрасывает заполнитель:
SELECT base64URLEncode('https://clickhouse.com') AS url_safe;
которая приезжает как aHR0cHM6Ly9jbGlja2hvdXNlLmNvbQ - два знака заполнителя стандартной формы срезаны. В остальных случаях рецепт - это два перевода символов и обрезка, и его стоит один раз оформить как функцию базы данных, потому что он нужен каждому конвейеру токенов. В PostgreSQL он выглядит так:
SELECT rtrim(replace(replace(
encode(convert_to('https://clickhouse.com', 'UTF8'), 'base64'),
'+', '-'),
'/', '_'),
'=') AS url_safe;
Переводите + в -, / в _, срезайте заполнитель в хвосте - готово. Одно предупреждение для сообщества SQL Server: вывод url_safe - это не то, чего ждут собственные XML- и JSON-base64-декодеры сервера, так что колонка, упакованная в URL-безопасной форме для внешнего мира, не распакуется внутри базы встроенными средствами. Держите в уме аудиторию, прежде чем выбирать алфавит.
JWT: печать токенов из базы данных
Самая интересная вещь, которую можно собрать энкодером, - это JSON Web Token, потому что JWT - это ничего иного, как три base64-куска подряд: заголовок и пелод, оба JSON-объекта, упакованные в URL-безопасной форме без заполнителя, и подпись, вычисленная по первым двум. Когда пакетной задаче нужно чеканить токены (засеивание тестовой среды, регенерация просроченных API-учётных данных, сборка потока аудита), вся церемония помещается в один запрос PostgreSQL, если вы примете pgcrypto для HMAC (включите его один раз через CREATE EXTENSION IF NOT EXISTS pgcrypto;):
WITH head AS (
SELECT encode(convert_to('{"alg":"HS256","typ":"JWT"}', 'UTF8'), 'base64') AS h
),
body AS (
SELECT encode(convert_to('{"sub":"1234567890","name":"Dev User"}', 'UTF8'), 'base64') AS p
),
joined AS (
SELECT rtrim(replace(replace(h, '+', '-'), '/', '_'), '=') AS h64u,
rtrim(replace(replace(p, '+', '-'), '/', '_'), '=') AS p64u
FROM head, body
)
SELECT h64u || '.' || p64u || '.' ||
rtrim(replace(replace(
encode(hmac((h64u || '.' || p64u)::bytea, 'sql-secret-key'::bytea, 'sha256'), 'base64'),
'+', '-'),
'/', '_'),
'=') AS token
FROM joined;
Каждый шаг - один из ходов, которые эта статья уже показала: упакуй JSON как base64, переформатируй его в URL-безопасный алфавит без заполнителя, затем подпиши первые два куска и переформатируй подпись тем же образом. Для JSON выше и секрета sql-secret-key результат - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRldiBVc2VyIn0.7_3VdmM8vH0L2bRBNqXXQXzZIR1l8T4_S4QxPPNBEFM, токен, который примет любой HS256-инспектор. Оговорки заслуживают столько же эфира, сколько и трюк: это покрывает только HMAC-алгоритмы (HS256, HS384, HS512), кладёт общий секрет внутрь выражения базы данных и сделан для пакетной работы и аудита, а не для производственного сервиса токенов. Сторона верификации, где вы доказываете токен против его секрета, - работа прикладного слоя или проверки подписи из статьи о декодировании.
Изображения и файлы в текстовой колонке
Самая частая причина кодировать в SQL - это файл, которому предстоит путешествовать текстом: API, которое встраивает изображение вместо ссылки на него, экспорт для системы, которая не несёт бинарного, скрипт-засевщик, который воссоздаёт базу данных на новом сервере. DuckDB делает круговой путь почти тривиальным, потому что он читает файлы в BLOB через табличную функцию, принимающую glob-шаблоны, а энкодер разравнивает всё, что приедет:
SELECT filename, to_base64(content) AS b64
FROM read_blob('/data/pics/*.png');
Одна строка на файл, плоская base64-строка на строку, никакой обёртки, которую нужно вырезать, хотя привычные один-два знака заполнителя едут вместе в хвосте. Запишите результат в текстовую колонку - и изображения переносимы через любой канал, который двигает текст. Затем честно проведите разговор о цене: фото на 1 мегабайт приезжает примерно 1.33 мегабайтом букв, и с этого момента каждый проход, сортировка и запись индекса платят эту цену. Если вы управляете схемой, лучший дизайн - колонка BLOB плюс кодирование на границе API, где наряжаются только те байты, которые реально покидают здание.
HTTP, JSON и API-трафик
Заголовки и пелоды - то место, где base64 делает свою тихую повседневную работу. Заголовок Basic auth - это литеральный префикс Basic , за которым следует base64 от username:password, и собрать его в SQL - это конкатенация плюс одно кодирование:
SELECT 'Basic ' || encode(convert_to('alice' || ':' || 's3cret', 'UTF8'), 'base64') AS header;
который собирает Basic YWxpY2U6czNjcmV0 - тот самый заголовок, который отправил бы клиент. Используйте его, чтобы генерировать фикстуры, с которыми сравнивают ваши интеграционные тесты, или чтобы нормализовать колонку сохранённых заголовков перед их аудитом. Со стороны JSON, MySQL может упаковать поле и встроить его в документ одним оператором, без прикладного кода в цикле:
SELECT JSON_OBJECT('img', TO_BASE64(CAST('hello file' AS BINARY))) AS doc;
Результат - {"img": "aGVsbG8gZmlsZQ=="}, готовый к отправке пелод. Та же форма работает для сертификатов, открытых ключей и любого другого файла, который ваше API решило встроить, и это то направление, которое важно, когда трафик производите вы, а не декодируете чужой.
Конфиг-файлы, секреты и переменные окружения
Одна привычка экспорта заслуживает отдельного абзаца, потому что она повсюду: секрет, сохранённый в виде base64 в таблице конфигурации. Kubernetes держал эту привычку живой, где значения секретов хранятся в base64, чтобы помещаться на одной строке YAML без кавычек, переносов и обратных слешей, и каждая внутренняя конфигурационная система, когда-либо встретившая Kubernetes-конвейер, подхватила её. Направление упаковки - одно кодирование на значение, при этом бинарное приведение делает работу с кодировкой, так что экспортируемый текст - ровно сохранённые байты:
SELECT name, TO_BASE64(CAST(value AS BINARY)) AS for_config
FROM app_config
WHERE name LIKE '%_secret%';
Каждое значение до 57 байт выходит плоской строкой, готовой к вставке; всё длиннее требует одного REPLACE(), чтобы вырезать строки обёртки, прежде чем оно уйдёт в YAML, а новая среда декодирует его обратно на другом конце. Отнеситесь к результату с той заботой, которую он заслуживает: вы только что превратили колонку сохранённых секретов в колонку сохранённых секретов, которую любой человек прочитает примерно за десять секунд, и привычке держать base64 в конфиге полагается второй взгляд в тот миг, когда вы стоите так близко к открытому тексту. Base64 - это транспорт, а не хранилище. Если в среде есть настоящее хранилище секретов, base64-колонка - это миграция от него.
Почта и привычка к 76 символам
Обёртка на 76 символов старше каждой базы данных на этой странице. MIME, набор стандартов, позволяющий почте нести бинарные вложения (RFC 2045, раздел 6.8, 1996), оборачивает base64-вывод на 76 символах и завершает каждую строку возвратом каретки и переводом строки, потому что старую почтовую сеть нельзя было доверять строкам длиннее. Три энкодера здесь унаследовали обёртку как своё значение по умолчанию (MySQL, PostgreSQL, SQLite CLI), и это подарок для всего, что в итоге попало в письмо, и ловушка для всего остального. И унаследовали они её наполовину готовой: PostgreSQL завершает свои строки одиночным переводом строки, а не возвратом каретки и переводом строки, как предписывает MIME-стандарт, так что выводу, которому полагается вставать в настоящее почтовое вложение, нужен ещё один проход:
WITH t AS (
SELECT replace(encode(attachment::bytea, 'base64'), chr(10), '') AS s
FROM email_outbox
)
SELECT regexp_replace(s, '(.{1,76})', '\1' || chr(13) || chr(10), 'g') AS mime_ready
FROM t;
Вырежьте переводы строк, которые PostgreSQL уже добавил, затем переоберните на 76 с полным CRLF после каждой строки, и строка станет MIME-корректной одним оператором. Прогоните через неё триста байтов, и 400 символов, которые вы упаковали, станут 412: шесть обёрнутых строк, шесть пар CRLF, двенадцать символов транспортной церемонии. Та же форма проблемы вылезает с PEM-блоками, которые обёртываются на 64 вместо 76, и с API, которым не нужна обёртка вообще, потому что их JSON-парсер не согласится встретить перевод строки посреди поля. Правило для всех: выясните, какой договор подписал потребитель, до того как кодировать, потому что переобёртка колонки сохранённого base64 - это миграция, а не запрос.
Ловушки: где энкодеры лгут вам
Каждая ловушка в этом списке установлена конкретным диалектом, а не base64, и у каждой из них есть как минимум одна кодовая база, которая нашла её на прод-сервере:
- Незапрошенная обёртка. MySQL и PostgreSQL по умолчанию обёртывают свой вывод на 76, SQLite CLI - на 72, и никто в вашем запросе этого не просил. Base64-поле в вашем JSON теперь содержит переносы строк, и потребитель, который принимает формат по спецификации, отклоняет его в дикой природе. Плоская форма - один
REPLACE()по символу перевода строки, применённый на стороне писателя, чтобы колонка хранила то, чего хочет читатель. - Помарка с кодировкой.
TO_BASE64('héllo')без бинарного приведения кодирует то, во что, по мнению кодировки соединения, являются буквы, а клиентlatin1и клиентutf8mb4думают о разном. Один и тот же текст запроса, два разных base64-результата, и неправильный декодируется в кракозябры, которые никто не отследит до энкодера. Бинарное приведение или явныйconvert_to()- единственная честная версия запроса. - BLOB-дверь.
to_base64()в DuckDB хочетBLOB: подайте ему текст - он сделает неявное приведение, так что колонкаvarcharс не-UTF-8 кодировкой может молча закодировать неправильные байты. Честный путь -to_base64(encode(...)), и поэтому примеры на этой странице всегда показывают пару для текстового входа. - Смена в 26.7: пробельные символы.
base64Decode()иbase64URLDecode()в ClickHouse годами были строгими к вводу, но с 26.7 они игнорируют пробельные символы (пробел, табуляция, перевод строки, возврат каретки, перевод формы), а не отбраковывают, так что скрипт, который раньше падал с ошибкой на обёрнутой колонке, теперь молча завершается успешно, что само по себе регрессия своего рода. Проверяйте версию сервера, прежде чем доверять декодированию. - Граница в 6000 байт.
BASE64_ENCODE()в SQL Server возвращаетvarchar(8000), когда вход -varbinary(n)с n до 6000, иvarchar(max)сверх этого; маппинг идёт по заявленному размеру, а не по значению, так что колонкаvarbinary(8000)возвращаетvarchar(max)даже на трёх байтах. Между тем выводurl_safeтой же функции не читается собственными XML- и JSON-base64-декодерами сервера, которые ждут стандартный алфавит с заполнителем. Выбирайте вариант под аудиторию, которая будет читать. - RAW на 2000 байт. В Oracle значение
RAWв простом SQL-операторе ограничено 2000 байтами. Поскольку энкодер принимаетRAWи возвращаетRAW, кодирование в одном операторе может принять лишь около 1500 байт входа (его 2000 символов вывода бы влезли, всё, что больше, - нет), а декодирование в одном операторе может принять лишь 2000 символов base64. Большие пелоды уходят в PL/SQL, где переменнаяRAWдержит 32767 байт, и один вызов уносит большую их часть, и только пелоды, чей base64-вывод превысил бы этот потолок, требуют чанк-цикла, который восходит к ограничению 1990-х, которое никогда не двигалось. - Налог на пакет. MySQL засчитывает закодированную строку в
max_allowed_packet, а не сырые байты. Фото, которое влезает в таблицу с лихвой, может переполнить пакет, став на 33 процента больше и обёрнутым, и режим падения - обрезанное значение илиNULL, которые выглядят как порча данных. Проверяйте лимит вместе с шириной колонки. - Перенос в хвосте.
base64()в SQLite CLI завершает последнюю строку переводом строки, привычка окончания строк применена и к финальной строке. Вставьте вывод оболочки в JSON-поле - и вы отправили base64-строку с переводом строки внутри, незапрошенная обёртка в другой роли. - Допущение про алфавит. Потребитель, собранный под стандартный алфавит, встречает ваш URL-безопасный вывод (или наоборот) и видит символы, которых не знает. Большинство декодеров громко падают на подчёркивании; некоторые молча, пропуская его. Документируйте алфавит каждой base64-колонки в комментарии схемы, потому что следующий разработчик не будет помнить, какой конвейер токенов написал строку.
Когда размер действительно важен
Расчёт размера - это плоская длина плюс всё, что добавляет обёртка вашего энкодера, а домашняя страница делает полное выведение пропорции. Что стоит сделать здесь - пройти по местам, где число перестаёт быть любопытством. Колонка VARCHAR, размер которой равен числу байтов входа, молча обрезает вывод в первый раз, когда пелод становится достаточно длинным, чтобы понадобился запас, потому что три байта входа стоят четырёх символов. Индекс по base64-текстовой колонке платит налог дважды: один раз в хранении и ещё раз в каждом сравнении, потому что записи индекса - это обёрнутые буквы, а не байты. max_allowed_packet MySQL и 1-ГБ потолок bytea PostgreSQL - две стены, в которые чаще всего упираются, и обе проверяются по тексту, который есть большая сторона обмена. Ответ на уровне проектирования редко про выбор другого энкодера (base64 всего один); он про выбор места, где происходит кодирование. BLOB-колонка, хэш-колонка для поиска, кодирование на границе: base64 существует только в трафике, где ему и положено.
Безопасность: чем не является base64
Base64 - не шифрование, и одна привычка, которую нужно произнести вслух, - это привычка с таблицей конфигурации из предыдущего раздела: секрет, сохранённый в base64, - это секрет, сохранённый другим шрифтом. Преобразование - биекция без ключа, обратимая каждым языком программирования на свете одним вызовом функции, и её единственный реальный эффект - держать значение на одной строке YAML. Если модель угроз включает другого пользователя этой базы, другой сервис, читающий экспорт, или журнал, схвативший строку, base64 вносит в защиту ровно ноль. Он скрывает значение от человеческого глаза на несколько секунд, поэтому в ревью кода он ощущается как защита, и поэтому же падает при инциденте. Шифруйте то, что должно быть секретом, шифруйте его ключом, который кто-то действительно может держать в тайне, и позвольте base64 делать то, что он умеет: двигать байты через канал, который несёт только текст.
Когда каждый диалект выучил обёртку
Заметки о выпусках рассказывают ту же историю, что и сторона декодирования, только буквы идут в обратную сторону, а расписание кое-что говорит о каждом движке:
2002. PostgreSQL 7.2 числит base64 форматом первого класса encode() и decode() - одновременно с UTL_ENCODE Oracle в эпоху 9i, и старейший base64-механизм в этой семье с небольшим отрывом. База данных с настоящим бинарным типом и аргументом формата добралась туда рано, потому что ответ был на расстоянии одного значения перечисления.
Начало 2000-х. Пакет UTL_ENCODE Oracle отгружается в эпоху 9i, с BASE64_ENCODE() рядом с его MIME-заголовком, quoted-printable и uudecode-братьями. RAW на входе, RAW на выходе, и спустя четверть века пакет не изменил мнения.
2013. MySQL 5.6 добавляет TO_BASE64() и FROM_BASE64() парой, а MariaDB 10.0 наследует обе. Договор пары с тех пор не сдвинулся: строки по 76 символам на выходе, терпимость к пробельным символам на входе.
2018. ClickHouse 18.16 (декабрь 2018) отгружает base64Encode() и base64Decode() вместе с алиасом в MySQL-стиле, потому что колоночный мир импортировал нагрузки, в схемах логов которых уже был base64.
2023. SQLite 3.41.0 добавляет base64() в командную оболочку как функцию, определённую приложением. Ядро, как водится, не получает ничего; оболочка, где люди на самом деле тыкают в файлы SQLite, получает инструмент.
2025. SQL Server 2025, ставший общедоступным в ноябре 2025 года, наконец отгружает BASE64_ENCODE() и BASE64_DECODE() - через тридцать шесть лет после запуска продукта и через поколение после того, как его пользователи выучили XML-обходной путь наизусть.
Паттерн - тот же, которым заканчивается статья о декодировании, но в зеркальном отражении: движки с настоящим бинарным типом и аргументом формата получили base64 в день, когда потребность стала очевидной, а движки, где всё - строки, спланировали его на потом.
Диковинки, которые стоит знать
base64()в SQLite CLI - мастер смены форм в этой семье, и в направлении кодирования трюк виден лучше всего: подайте емуBLOB- он вернёт обёрнутый текст с переводом строки в хвосте, подайте текст - он вернётBLOB. Одно имя, две работы, выбор по типу аргумента, и ни один другой энкодер в этой семье так не умеет.- Семья
base64Decode()в ClickHouse стала снисходительной в 26.7: пробельные символы во вводе теперь игнорируются, а не отбраковываются, так что один и тот же запрос на обёрнутой колонке падает на старом сервере и молча возвращает значение на новом. Декодер не сломался; он расслабился, что, как ни странно, отлаживать труднее. - PostgreSQL оборачивает на 76 символах ровно как MIME-стандарт 1996 года, только завершает строки одиночным переводом строки вместо возврата каретки и перевода строки из стандарта. Более двадцати лет после спецификации - на один символ меньше на строку, и бунт незрим, если не сделать дифф байтов.
- В клиенте
mysqlзакодированные вами байты печатаются нормально как base64-текст, но в тот момент, когда вы смотрите на сырую колонку черезCAST(... AS BINARY), клиент переключается на шестнадцатеричное отображение (binary-as-hex), и совершенно здоровоеhelloприезжает на экран как0x68656C6C6F. Эта настройка убедила тысячи разработчиков, что их энкодер сломан. - Snowflake отображает значения
BINARYкак шестнадцатеричные в любом наборе результатов, так что входная колонкаTO_BINARY()в вашем запросе кодирования читается как контрольная сумма, даже когда всё сработало. Два диалекта, два шестнадцатеричных отображения, одно и то же чувство тревоги. - SQL-уровневый
RAWв Oracle ограничен 2000 байтами, так что сертификат на 3 килобайта вообще нельзя вставить в SQL-оператор какRAW-литерал. Кодирование должно происходить в PL/SQL, где переменнаяRAWдержит 32767 байт, так что сертификат на 3 килобайта - один вызов, и только пелоды, чей base64-вывод превысил бы 32 килобайта, требуют чанк-цикла, который восходит к 1990-м. - Одна строка MIME base64 - это 76 символов, то есть 57 сырых байтов, потому что четыре символа несут три байта. Число 76, которое встречается в значениях по умолчанию трёх энкодеров, - это не столько лимит, сколько плотность упаковки: каждая обёрнутая строка, которую вы видите в старом почтовом вложении, несла ровно 57 байтов ваших данных.
Другое направление
Эта статья была про надевание костюма: решение, какие байты вы имеете в виду, наблюдение за тем, что выходит, выбор алфавита под аудиторию и расчёт размера до того, как колонка обрежет. Снять костюм - совершенно другой характер, с тихими NULL, где один диалект пожимает плечами, с жёсткими ошибками, где другой повышает голос, и с URL-безопасным алфавитом, которого половина семьи не знает вовсе. Всё это, от FROM_BASE64() и decode() до BASE64_DECODE(), подробно разобрано в связанной статье о декодировании Base64 в SQL, на которую ведёт ссылка прямо ниже. Кодируйте здесь, декодируйте там - и весь круговой путь умещается в одно полдня.
Последнее обновление: 2026-09-08
Связанная статья: Декодирование Base64 в SQL: полное руководство