Ошибка #1071 Specified key was too long возникает, когда полный размер индекса превышает лимит, доступный для выбранного движка, формата строк и размера страницы InnoDB.
Сообщение с пределом 767 bytes обычно указывает на старую таблицу с ROW_FORMAT=COMPACT или ROW_FORMAT=REDUNDANT. Такое встречается после переноса базы со старой версии MySQL или MariaDB, даже если сам сервер уже обновлён.
Когда решение ниже не подходит: если ошибка относится не к InnoDB, в сообщении указан другой предел, используется нестандартный размер страницы или индекс создаётся ORM/фреймворком. Сначала выполните диагностику и проверьте фактическую структуру таблицы.
Почему появляется ограничение 767 байт
В старом файловом формате InnoDB Antelope использовались форматы строк REDUNDANT и COMPACT. Для них максимальная длина индексного ключа составляет 767 байт.
Кодировка utf8mb4 может использовать до четырёх байт на символ. Поэтому индекс по одному полю VARCHAR(255) потенциально требует:
255 × 4 = 1020 байт
Это больше 767 байт. Отсюда появился распространённый обходной вариант с длиной VARCHAR(191):
191 × 4 = 764 байта
Но число 191 не является универсальным правилом. Оно относится к одному индексируемому полю utf8mb4 при лимите 767 байт. В составном индексе учитывается суммарная максимальная длина всех его частей.
Пример ошибки
На таблице с форматом строк COMPACT следующий индекс может превысить старый лимит:
CREATE TABLE users_legacy ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(255) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_users_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;
Сервер вернёт сообщение, похожее на:
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
Сначала выполните диагностику
Проверьте версию сервера, размер страницы InnoDB, формат строк и фактические индексы проблемной таблицы:
SELECT VERSION(); SHOW VARIABLES LIKE 'innodb_page_size'; SHOW VARIABLES LIKE 'innodb_default_row_format'; SHOW TABLE STATUS LIKE 'users'; SHOW CREATE TABLE users; SHOW INDEX FROM users;
Для старых MySQL и MariaDB дополнительно проверьте:
SHOW VARIABLES LIKE 'innodb_file_per_table'; SHOW VARIABLES LIKE 'innodb_file_format'; SHOW VARIABLES LIKE 'innodb_large_prefix';
В MySQL 8 и современных MariaDB переменные innodb_file_format и innodb_large_prefix удалены либо оставлены только для совместимости. Пустой результат такого запроса сам по себе не является ошибкой.
Современные MySQL и MariaDB
Для таблиц InnoDB с ROW_FORMAT=DYNAMIC или ROW_FORMAT=COMPRESSED при стандартной странице 16 КБ максимальная длина индексного ключа составляет 3072 байта.
В MySQL 8 формат DYNAMIC используется по умолчанию, если настройка innodb_default_row_format не была изменена. Современные версии MariaDB также используют DYNAMIC как стандартный формат.
Если старая таблица сохранила COMPACT, переведите её в DYNAMIC:
ALTER TABLE users ENGINE=InnoDB, ROW_FORMAT=DYNAMIC;
После перестроения таблицы можно создать полный индекс по VARCHAR(255) в utf8mb4:
ALTER TABLE users ADD UNIQUE KEY uq_users_email (email);
Не меняйте utf8mb4 на utf8/utf8mb3 только ради уменьшения индекса. Трёхбайтовая кодировка не поддерживает весь Unicode, включая часть эмодзи и редких символов.
Старые MySQL и MariaDB: Antelope и Barracuda
На старых серверах увеличение лимита требовало сочетания нескольких условий:
innodb_file_per_table=ON;- файловый формат
Barracuda; innodb_large_prefix=ON;ROW_FORMAT=DYNAMICилиROW_FORMAT=COMPRESSED.
Исторический пример конфигурации для MySQL 5.6 и старых MariaDB:
[mysqld] innodb_file_per_table=1 innodb_file_format=Barracuda innodb_large_prefix=1
После изменения конфигурации сервер перезапускали, а таблицу перестраивали:
ALTER TABLE users ENGINE=InnoDB, ROW_FORMAT=DYNAMIC;
Важно: не добавляйте эти параметры в конфигурацию MySQL 8 или современной MariaDB вслепую. Они относятся к историческим версиям и в новых выпусках не управляют лимитом индекса.
Быстрый вариант: уменьшить индексируемую длину
Если обновить сервер или перестроить таблицу пока невозможно, можно сократить поле до 191 символа:
CREATE TABLE users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(191) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_users_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Такой вариант подходит только тогда, когда бизнес-правила действительно допускают максимум 191 символ. Например, для полного адреса электронной почты это может оказаться слишком жёстким ограничением.
Префиксный индекс
Для длинного текста, URL или slug иногда достаточно индексировать начало значения:
CREATE INDEX idx_pages_slug ON pages (slug(191));
Префиксный индекс уменьшает размер и может ускорить поиск по началу строки. Однако UNIQUE INDEX по префиксу гарантирует уникальность только индексируемой части. Два разных значения с одинаковыми первыми 191 символами будут считаться дубликатами.
Составные индексы
Ошибка может появиться, даже если каждое поле отдельно короче допустимого предела:
CREATE INDEX idx_catalog_filter ON catalog (category(100), slug(150));
Для utf8mb4 теоретический размер строковой части такого индекса составляет:
(100 + 150) × 4 = 1000 байт
При лимите 767 байт индекс не создастся. Решение выбирают по реальным запросам:
- сократить один или оба префикса;
- изменить порядок столбцов;
- удалить лишнюю часть индекса;
- перевести таблицу в
DYNAMIC; - создать отдельный хеш для точного поиска длинного значения.
Миграция старой базы
1. Создайте резервную копию
mysqldump \ --single-transaction \ --routines \ --triggers \ --events \ database_name > database_name-before-row-format.sql
2. Найдите старые таблицы
SELECT
TABLE_SCHEMA,
TABLE_NAME,
ENGINE,
ROW_FORMAT,
TABLE_COLLATION
FROM information_schema.TABLES
WHERE ENGINE = 'InnoDB'
AND ROW_FORMAT IN ('Compact', 'Redundant')
AND TABLE_SCHEMA NOT IN (
'information_schema',
'mysql',
'performance_schema',
'sys'
)
ORDER BY TABLE_SCHEMA, TABLE_NAME;
3. Проверьте индексы проблемной таблицы
SELECT s.INDEX_NAME, s.SEQ_IN_INDEX, s.COLUMN_NAME, s.SUB_PART, c.COLUMN_TYPE, c.CHARACTER_SET_NAME, c.COLLATION_NAME FROM information_schema.STATISTICS AS s INNER JOIN information_schema.COLUMNS AS c ON c.TABLE_SCHEMA = s.TABLE_SCHEMA AND c.TABLE_NAME = s.TABLE_NAME AND c.COLUMN_NAME = s.COLUMN_NAME WHERE s.TABLE_SCHEMA = 'database_name' AND s.TABLE_NAME = 'users' ORDER BY s.INDEX_NAME, s.SEQ_IN_INDEX;
4. Перестройте одну таблицу и проверьте результат
ALTER TABLE database_name.users ENGINE=InnoDB, ROW_FORMAT=DYNAMIC; SHOW TABLE STATUS FROM database_name LIKE 'users'; SHOW CREATE TABLE database_name.users; CHECK TABLE database_name.users;
После проверки повторите операцию для остальных таблиц. Не запускайте массовый ALTER TABLE на production без оценки времени и свободного места.
Краевые случаи
- При размере страницы InnoDB 8 КБ лимит для большого индекса снижается до 1536 байт, при 4 КБ — до 768 байт.
- Ограничение применяется и к полному индексу столбца, и к индексируемому префиксу.
- Составной индекс расходует лимит суммарно по всем частям.
- После обновления сервера старые таблицы могут сохранить
COMPACTи старое ограничение. ALTER TABLEможет перестроить всю таблицу, временно увеличить расход диска и создать блокировки.- ORM может повторно создать слишком длинный индекс при следующей миграции. Исправление нужно внести и в схему приложения.
- Префиксный уникальный индекс не заменяет проверку полного значения.
Безопасность миграции
- Проверьте резервную копию восстановлением на тестовом сервере.
- Не формируйте имена таблиц и индексов из пользовательского ввода.
- Выполняйте изменение схемы учётной записью с минимально необходимыми правами.
- Для большой production-таблицы заранее проверьте доступность online DDL или используйте специализированный инструмент миграции без длительной блокировки.
- Сначала повторите изменение на staging-копии с сопоставимым объёмом данных.
Производительность
Широкий индекс занимает больше места на диске и в буферном пуле, увеличивает стоимость INSERT, UPDATE и репликации. Даже при доступном лимите 3072 байта не стоит автоматически индексировать длинные строки целиком.
Проверьте реальные запросы через EXPLAIN и оставьте в индексе только те столбцы и префиксы, которые участвуют в поиске, сортировке или обеспечении уникальности.
Совместимость
- MySQL 5.6 и старше: часто требуется Barracuda,
innodb_large_prefix,innodb_file_per_tableи форматDYNAMIC/COMPRESSED. - MySQL 5.7: поддерживает индекс до 3072 байт для
DYNAMIC/COMPRESSED; в старых таблицах может сохранитьсяCOMPACT. - MySQL 8.x:
DYNAMICявляется стандартным форматом, старые настройки файлового формата удалены. - MariaDB 10.2 и старше: поведение зависит от
innodb_large_prefixи формата строк. - Современные MariaDB: большие префиксы поддерживаются для
DYNAMIC, а старые переменные не следует использовать как рабочее исправление.
Проверка решения
Создайте тестовую таблицу с теми же кодировкой, форматом строк и индексом, что используются в проекте:
DROP TABLE IF EXISTS index_length_test; CREATE TABLE index_length_test ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(255) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uq_index_length_test_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; SHOW CREATE TABLE index_length_test; CHECK TABLE index_length_test; DROP TABLE index_length_test;
Если таблица и индекс создаются без ошибки, а SHOW CREATE TABLE показывает ожидаемый формат, выбранная конфигурация поддерживает этот индекс.
Что выбрать
- Современный сервер и старая таблица: перевести таблицу в
ROW_FORMAT=DYNAMIC. - Старый сервер: проверить Barracuda,
innodb_large_prefixиinnodb_file_per_table, затем запланировать обновление. - Индекс нужен только для поиска по началу строки: использовать разумный префикс.
- Нужна точная уникальность длинного значения на старом сервере: хранить полный текст отдельно, а для индекса использовать вычисленный приложением бинарный хеш с дополнительной проверкой исходного значения.
- Индекс слишком широкий даже при лимите 3072 байта: пересмотреть структуру и назначение индекса.
Комментарии
0Нашли ошибку или хотите дополнить материал?