Покупатель оставил отзыв на сайте, добавил в конце улыбающийся смайлик и нажал «Отправить». В результате отзыв либо не сохраняется, либо вместо эмодзи появляются четыре вопросительных знака, либо текст обрывается ровно на этом месте. Такое чаще всего встречается в старых или неправильно настроенных базах MySQL, и причина почти всегда одна: таблица или соединение работают не в кодировке utf8mb4. В этой статье разберём, откуда берётся проблема, как она касается узбекского алфавита и как безопасно перевести базу.
«utf8» в MySQL — это не полноценный UTF-8
В стандарте UTF-8 один символ занимает от 1 до 4 байт. Латиница и цифры — 1 байт, кириллица (включая ў, қ, ғ, ҳ) — 2 байта, многие специальные символы — 3 байта, а эмодзи и часть редких иероглифов — 4 байта. Исторически MySQL под именем utf8 реализовал урезанный вариант, который хранит символы не длиннее 3 байт; в современной документации он называется utf8mb3. Полноценный UTF-8 в MySQL называется utf8mb4.
Поэтому если таблица создана в utf8, четырёхбайтовый символ в неё просто не помещается. В строгом режиме (strict mode) запрос завершается ошибкой, в нестрогом символ заменяется вопросительным знаком или строка обрезается. В журнале ошибок обычно видно примерно следующее:
SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\x80' for column 'comment' at row 1
Байты \xF0\x9F\x98\x80 — это и есть смайлик в кодировке UTF-8. Данные пришли корректно, просто столбец не смог их вместить.
Где на самом деле ломаются узбекские буквы
Многие думают, что узбекские oʻ и gʻ ломаются так же, как эмодзи, но ситуация другая. Буквы o' и g', набранные обычным апострофом, состоят из однобайтовых символов, специальный знак ʻ (U+02BB) занимает 3 байта, кириллические ў и қ — по 2 байта. Все они нормально хранятся даже в таблице utf8 (mb3). Узбекский текст обычно портится по другой причине: таблица создана в latin1 или cp1251, либо PHP не указывает кодировку при подключении к базе. Тогда кириллица записывается как «????», и восстановить её потом уже невозможно.
Итог простой: переход на utf8mb4 закрывает сразу две проблемы — эмодзи и прочие четырёхбайтовые символы начинают сохраняться, а старые таблицы в latin1 приводятся к современной кодировке. Для новых проектов выбирать что-то другое нет смысла.
Как проверить текущее состояние
Перед изменениями нужно точно выяснить, в какой кодировке находятся база, таблицы и столбцы. Сделать это можно в phpMyAdmin или SQL-запросом. Следующий запрос покажет collation каждой таблицы; начало имени collation обозначает кодировку:
SELECT TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'example_db';
SHOW VARIABLES LIKE 'character_set%';
SHOW FULL COLUMNS FROM comments;
Если вы видите latin1_swedish_ci или utf8_general_ci, нужна конвертация. Если там уже utf8mb4_unicode_ci или utf8mb4_0900_ai_ci, таблицы в порядке, и проблема, скорее всего, в настройках соединения.
Порядок перевода базы на utf8mb4
Перед любыми изменениями сделайте полную резервную копию базы. В панели хостинга это несколько кликов, по SSH используется mysqldump. Кодировку дампа важно указать явно, иначе испортиться может сам файл копии:
mysqldump --default-character-set=utf8mb4 -u example_user -p example_db > example_db_backup.sql
Затем выполняются команды для базы и каждой таблицы. CONVERT TO переводит все текстовые столбцы таблицы в новую кодировку и перекодирует существующие данные:
ALTER DATABASE example_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE products CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Если таблиц много, удобнее не писать команды вручную, а сгенерировать их запросом. Результат копируете и выполняете отдельно:
SELECT CONCAT('ALTER TABLE `', TABLE_NAME, '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;')
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'example_db';
Ошибка длины индекса
На старых версиях MySQL (5.6 и ниже, особенно с форматом строк COMPACT) при конвертации может появиться ошибка «Specified key was too long; max key length is 767 bytes». Причина в том, что в utf8mb4 на символ резервируется 4 байта, и индекс по столбцу VARCHAR(255) требует 1020 байт. В MySQL 5.7 и новее с форматом DYNAMIC предел вырос до 3072 байт, и проблема почти не встречается. На старом сервере обычное решение — сократить индексируемый столбец до VARCHAR(191).
Кодировка в PHP-подключении
Конвертация таблиц — только половина дела. Если приложение не сообщает кодировку при подключении, сервер использует значение по умолчанию, и эмодзи снова ломаются. Для PDO в DSN добавляется charset, для mysqli вызывается отдельный метод:
// PDO
$pdo = new PDO('mysql:host=localhost;dbname=example_db;charset=utf8mb4', $user, $pass);
// mysqli
$db = new mysqli('localhost', $user, $pass, 'example_db');
$db->set_charset('utf8mb4');
В WordPress настройка находится в wp-config.php: define('DB_CHARSET', 'utf8mb4');. В Laravel значения charset и collation в config/database.php по умолчанию уже utf8mb4, но в старых проектах их стоит проверить. Строки SET NAMES utf8 в старом коде тоже нужно заменить на SET NAMES utf8mb4.
Пример: цветочный магазин в Ташкенте
Представим службу доставки цветов в Ташкенте с сайтом misol.uz и формой заказа. Покупатели часто добавляют в текст поздравления 🌹 и 🎉. Сайт сделан несколько лет назад, таблица orders в utf8_general_ci. В итоге заказы с эмодзи не попадали в базу, и оператор их вообще не видел. Разработчик сделал резервную копию, перевёл таблицы orders и order_items через CONVERT TO, добавил charset=utf8mb4 в подключение PDO и отправил тестовый заказ. После этого поздравления стали сохраняться полностью, вместе с эмодзи.
Такую работу лучше делать в часы минимальной посещаемости: конвертация большой таблицы может занять несколько минут, и всё это время таблица может быть заблокирована. На хостинге sayt.uz с базами можно работать через phpMyAdmin, а панель открывается из кабинета sayt.uz.
Что проверить после конвертации
- Сохраните запись с эмодзи, кириллическими ў, қ и латинскими o', g' и посмотрите, как она выглядит на странице.
- Убедитесь, что в HTML есть
<meta charset="utf-8">, иначе браузер может интерпретировать текст иначе. - Проверьте поиск и сортировку: при смене collation меняются и правила сравнения строк.
- Если до конвертации текст уже был сохранён как «????», сменой кодировки его не вернуть — поможет только восстановление из резервной копии.
Последний пункт стоит подчеркнуть особо: utf8mb4 защищает будущие данные, но не возвращает уже потерянные символы. Поэтому в новом проекте правильнее всего сразу создавать базу и таблицы с utf8mb4_unicode_ci. Если остались вопросы, можно обратиться в техподдержку sayt.uz через тикет.