Утро начинается с тревоги мониторинга: mysqld гаснет через пару секунд после старта, в журнале ошибок стена шестнадцатеричных кодов и фраза "Database page corruption on disk or a failed read". InnoDB держит все данные в согласованном наборе страниц, и одна битая страница останавливает сервер целиком. Хуже всего в такой минуте одно: любой эксперимент поверх повреждённого хранилища рискован, лишний перезапуск может добить остатки. Поэтому путь спасения строгий: прочитать журнал, снять копию файлов, поднять сервер в аварийном режиме, выгрузить уцелевшие строки, пересобрать хранилище и вернуть данные. Инструменты для этого ставятся за минуты, а порядок действий важнее скорости рук.
Признаки повреждения InnoDB в журнале ошибок сервера
Журнал всё рассказывает раньше, чем кто-то успеет гадать. Классическая картина после внезапного обесточивания или плохих секторов: старт проходит, журнал редо проигрывается, сервер отвечает и падает. Повтор запуска даёт тот же финал, в логе повторяется фраза про повреждение страницы на диске, а следом идёт дамп страницы текстом и hex-кодом. Бывает и мягкий вариант: сервер работает, но конкретный SELECT роняет соединение, потому что запрос попадает ровно в сломанный диапазон.
Причины разрушения страниц известны по годам практики: резкое обесточивание в момент записи, умирающие сектора диска, случайные перевёрнутые биты в плохой памяти, файловая система, потерявшая последние записи после зависания, и любимая ошибка админа - копирование файлов под работающим сервером. InnoDB пишет страницы по 16 килобайт и снабжает каждую контрольной суммой: несовпадение при чтении означает подозрительную страницу. При старте движок накатывает журнал редо поверх файлов; если в цепочке попадается битая страница, сервер сознательно останавливается, чтобы не писать поверх остатков. В выпусках 8.0, начиная с 8.0.30, файлы журнала редо живут в отдельном каталоге #innodb_redo, что стоит помнить при поиске по диску.
Первичный разбор журнала занимает минуты и определяет всё дальнейшее. В дампе страницы видны идентификатор табличного пространства и смещение, по ним виновник опознаётся быстрее, чем по догадкам. Стоит выписать и первые ошибки до падения: сообщения файловой системы о плохих секторах и жалобы на память намекают на первопричину, и без её устранения даже идеальная пересборка закончится тем же исходом.
Диагностика через mysqlcheck на ещё работающем сервере
mysqlcheck - клиент первой линии: он проверяет, чинит, оптимизирует и анализирует таблицы, заворачивая команды CHECK TABLE, REPAIR TABLE, ANALYZE TABLE и OPTIMIZE TABLE в удобную обёртку. Работает он только при живом сервере, а каждая таблица на время обработки закрывается от других сессий, поэтому на больших объёмах процедуру запускают в тихое окно.
mysqlcheck -u root -p --all-databases
mysqlcheck -u root -p --all-databases --auto-repair
mysqlcheck -u root -p --analyze shop orders
Нюанс, который ломает наивные планы: REPAIR TABLE не поддерживает InnoDB. Команда чинит MyISAM, ARCHIVE и CSV, а повреждённая InnoDB-таблица лечится только пересозданием данных. Роль mysqlcheck здесь - сортировка пострадавших: он показывает, какие базы целы, а какие отвечают ошибками. Если сервер вообще не стартует, mysqlcheck бессилен, и следующим шагом становится копия файлов.
Флаги клиента тоже стоит знать заранее: --extended прогоняет самую полную проверку и занимает больше времени, --optimize пересоздаёт таблицу, что для InnoDB означает пересборку со свежими индексами и статистикой, а --analyze обновляет статистику без пересборки. Вывод читается как сводка: строка OK означает целую таблицу, предупреждения идут с именем таблицы, так что отчёт удобно сохранить и сравнить до и после ремонта.
Резервное копирование файлов данных перед ремонтом
Железное правило аварийной работы: прежде чем менять что-либо, снять полную копию каталога данных. Уровни принудительного восстановления от четвёрки и выше сами по себе могут ухудшить состояние файлов, а любая ошибка обходится дешевле, когда испорченную копию можно заменить нетронутой. Копия всего каталога стоит одного свободного вечера и щепотки места на диске.
systemctl stop mysql
df -h /var/lib/mysql /srv/backup
cp -a /var/lib/mysql /srv/backup/mysql-broken-$(date +%F)
Сервер обязан быть остановлен: файлы живого InnoDB постоянно меняются, и в снимке окажутся полузаписанные страницы. Для чистой остановки опытные руки ставят innodb_fast_shutdown=0: движок при выключении делает полную очистку и слияние буфера изменений, файлы остаются согласованными. Если места на полную копию нет, сохраняют хотя бы системное табличное пространство и файлы .ibd самых важных баз, но это компромисс из отчаянных. Удалять ibdata1 в надежде на самовосстановление нельзя вовсе: вместе с ним пропадает словарь данных, и файлы таблиц превратятся в бесполезные обломки.
Ускорить физическую копию помогает LVM: снапшот логического тома с остановленным сервером создаётся за секунды, а копируют уже снимок, без риска расхождения файлов. Приём старый и рабочий, он же выручает перед крупными обновлениями схемы.
Аварийный режим innodb_force_recovery и его шесть уровней
Параметр живёт в секции [mysqld], по умолчанию равен нулю, а аварийные значения простираются от 1 до 6. Каждый следующий уровень включает возможности предыдущих и отнимает у движка больше способностей. При любом значении выше нуля InnoDB запрещает INSERT, UPDATE и DELETE: режим рассчитан на чтение и эвакуацию, а не на работу.
В Debian и Ubuntu строку вписывают в /etc/mysql/mysql.conf.d/mysqld.cnf, в RHEL и CentOS - в /etc/my.cnf; после правки службу перезапускают целиком, потому что этот параметр читается только при старте.
[mysqld]
innodb_force_recovery = 1
Уровень 1 (SRV_FORCE_IGNORE_CORRUPT) разрешает серверу работать вопреки обнаруженной битой странице: SELECT перепрыгивает повреждённые записи и страницы, что и помогает выгрузить уцелевшее. Уровень 2 (SRV_FORCE_NO_BACKGROUND) останавливает фоновые процессы уборки, которые сами способны завалить нестабильный сервер. Уровень 3 (SRV_FORCE_NO_TRX_UNDO) пропускает откат транзакций после аварийного восстановления, незавершённые операции остаются в файлах как есть.
Граница безопасности проходит сразу после тройки. На уровнях 1-3 теряется относительно немного, только содержимое конкретно битых страниц; доступны DROP и CREATE таблиц, что позволяет выбросить безнадёжную таблицу и завести её заново. Уровень 4 (SRV_FORCE_NO_IBUF_MERGE) отключает слияние буфера вставки и сбор статистики таблиц, переводит InnoDB в режим только чтения; с этого значения параметр способен необратимо испортить файлы, а после него готовьтесь пересоздавать вторичные индексы. Уровень 5 (SRV_FORCE_NO_UNDO_LOG_SCAN) не читает undo-журналы и считает незавершённые транзакции зафиксированными, режим тот же: только чтение и риск необратимой порчи. Уровень 6 (SRV_FORCE_NO_LOG_REDO) пропускает накат журнала редо, страницы остаются в устаревшем состоянии, и рассогласование добавляет искажения в B-деревья и прочие структуры; DROP TABLE на максимальном значении уже недоступен.
Официальная рекомендация сводится к одной строке: начинать с единицы и наращивать значение шагами ровно до того уровня, на котором сервер наконец стартует. Прыгать сразу на шестёрку не стоит даже в панике: уцелевшее обычно выгружается и на низких уровнях.
Дальше действует строгий порядок:
- остановить сервер и снять полную копию каталога данных, если её ещё нет;
- вписать innodb_force_recovery = 1 в секцию [mysqld] и попробовать старт;
- при неудаче поднимать значение по одному шагу с перезапуском сервера, пока он не оживёт;
- немедленно выгрузить дамп уцелевших баз, пока сервер отвечает на чтение;
- остановить сервер, убрать параметр, пересоздать каталог данных и вернуть всё из дампа.
Выгрузка уцелевших таблиц и пересборка сервера с нуля
Как только сервер поднялся хотя бы на единице, начинается эвакуация. Стандартный вызов выглядит так:
mysqldump -u root -p --all-databases --single-transaction --skip-lock-tables --routines --events > full_dump.sql
Флаг --single-transaction снимает согласованный снимок без блокировок, --skip-lock-tables нужен, потому что аварийный сервер не даст ставить блокировки как обычно, а --routines и --events подхватывают из дампа хранимые процедуры и события планировщика. Когда дамп падает на конкретной таблице, базы выгружают по одной, а внутри базы таблицы по очереди, виновника вытаскивают кусками по диапазонам первичного ключа, выбирая уцелевшие участки.
Дальше идёт чистая пересборка. Старый каталог убирают в сторону, новый инициализируют, дамп загружают:
systemctl stop mysql
mv /var/lib/mysql /srv/backup/mysql-broken-2
mysqld --initialize-insecure --datadir=/var/lib/mysql
systemctl start mysql
mysql -u root < full_dump.sql
После ключа --initialize-insecure учётка root существует без пароля, и сразу после загрузки дампа ей задают пароль командой ALTER USER. Пересборка заодно сулит бонусы: пропадают обрывки и дыры, пересчитывается статистика, вторичные индексы строятся с чистого листа. Про учётки отдельная история. Дамп с ключом --all-databases включает служебную базу mysql с паролями и правами, и когда версии старого и нового сервера совпадают, доступ возвращается сам. При смене мажорной версии служебную схему переносят аккуратно: права собирают командой SHOW GRANTS на старом экземпляре и накатывают на новый вручную, таблицы паролей между выпусками не всегда совместимы.
По окончании стоит прогнать mysqlcheck по всем базам и сверить счётчики строк с ожиданиями: сверка превращает удачную эвакуацию в проверенную.
Percona Toolkit для сверки данных и пересоздания таблиц
Актуальная ветка набора в октябре 2026 - 3.7, ставится он из репозиториев дистрибутива или от производителя.
sudo apt-get install percona-toolkit
В восстановительных работах первым идёт pt-table-checksum: инструмент сверяет данные реплик с мастером и находит молча пропавшие строки, такую проверку полезно запускать после каждой эвакуации. Его напарник pt-table-sync чинит расхождения точечно, без полной перезаливки реплики. Для подозрительной таблицы есть pt-online-schema-change: инструмент пересоздаёт таблицу на живом сервере без блокировки записи, а пересоздание по сути даёт свежую копию с новыми индексами. pt-archiver перекладывает и удаляет строки малыми порциями, им удобно вынести уцелевшую часть большой таблицы в новое место. Крайний эшелон диагностики снимают pt-stalk, который пишет состояние сервера в момент всплеска, и pt-mysql-summary, печатающий снимок конфигурации и статуса.
Совсем крайний случай - отдельный открытый проект undrop-for-innodb с утилитами stream_parser и c_parser. Они разбирают файлы .ibd на страницы и вытаскивают распознаваемые строки напрямую из остатков, без живого сервера. Работа это медленная и кропотливая, но когда дампа и резервных копий нет, часы парсинга лучше недель ручного восстановления записей по памяти.
Профилактика, которая снижает цену следующего сбоя
Любое восстановление заканчивается неудобным вопросом, почему уцелевшие данные вообще уцелели. Рабочий ответ - дисциплина. Физические копии InnoDB снимают горячим способом утилитами вроде XtraBackup, логический дамп держат вторым слоем на случай логического повреждения. Восстановление репетируют хотя бы раз в квартал: непроверенная копия не считается копией. Журнал ошибок и SMART дисков ставят под наблюдение, обновления сервера делают из чистой остановки с innodb_fast_shutdown=0, файлы работающего сервера никогда не копируют как резервную копию, а расхождения на репликах ловят расписанным pt-table-checksum. Отдельного внимания заслуживает нижний слой: кэширование записи без барьеров на виртуальных машинах повышает риск потерять последние записи при аварии хост-машины, и часть ночных сюрпризов родом именно оттуда. В MySQL 8 есть и встроенный плагин клонирования: он передаёт физическую копию между серверами по сети и разворачивает новую реплику за минуты вместо часов.
INSTALL PLUGIN clone SONAME 'mysql_clone.so';
Пусть сценарий из статьи никогда не понадобится в два часа ночи. Но снятая вовремя копия и проверенный восстановлением дамп надёжнее всякой надежды: сбой превращается из происшествия в рутинную процедуру с чек-листом и предсказуемым концом.