Блог

На продакшн сервере мы используем InnoDB движок на Mysql версии 5.5.28 под Ubuntu 10.04. На днях, а именно в новогодние праздники, случилась у нас потеря соединения с MySQL, которая привела к вылету взаимодействующих с БД модулей с ошибкой: «Lost connection to MySQL server during query».

Сразу перейти к решению

Казалось бы ничего страшного, требуется простой рестарт сервера баз данных и перезапуск демонов, благо целостность данных контролируется и нами, и движком в достаточной степени, поэтому ничего экстраординарного, вроде бы, не произошло. Однако, в этот раз MySQL отказался рестартовать, а запустил лишь error.logger и выдал сообщение следующего содержания (при этом тщетно пытаясь восстановить испорченные данные):

InnoDB: Page checksum 3630348073 (32bit_calc: 1117368520), prior-to-4.0.14-form checksum 2506779514 
InnoDB: stored checksum 2904142024, prior-to-4.0.14-form stored checksum 2506779514 InnoDB: Page lsn 2350 752825580, low 4 bytes of lsn at page end 752825580
InnoDB: Page number (if stored to page already) 515873, 
InnoDB: space id (if created with >= MySQL-4.1.1 and stored already) 841190 
InnoDB: Page may be an index page where index id is 3365 
InnoDB: (index "idx_hash" of table "db_test"."test") 
InnoDB: Database page corruption on disk or a failed 
InnoDB: file read of page 515873. 
InnoDB: You may have to recover from a backup. 
InnoDB: It is also possible that your operating system has corrupted its own file cache 
InnoDB: and rebooting your computer removes the error. 
InnoDB: If the corrupt page is an index page you can also try to fix the corruption InnoDB: by dumping, dropping, and reimporting the corrupt table. You can use CHECK 
InnoDB: TABLE to scan your table for corruption. 
InnoDB: See also http://dev.mysql.com/doc/refman/5.5/en/forcing-innodb-recovery.htmlInnoDB: about forcing recovery. 
InnoDB: Database page corruption on disk or a failed
InnoDB: file read of page 515873. 
InnoDB: You may have to recover from a backup. 
InnoDB: Page dump in ascii and hex (16384 bytes): 
mysqld:  len 16384; hex ad19b0c80007df210007df27000b9e070000092e2cdf34ec45b...

Перед тем как ринуться в бой, позволю себе привести список основных ошибок MySQL и их причин, который поможет определиться с нашей ситуацией.

По информации с  http://www.datarecovery-info.com/database-recovery/mysql-recovery.html основными ошибками являются:

  • error #1142 - SELECT command denied to user 'xyz'@'localhost' for table 'employee' 
  • InnoDB:The log sequence number in ibdata files does not match 
  • Index file is crashed/ Wrong file format 
  • Database page corruption on disk or a failed file read. 
  • No more room in record/index file 
  • Incorrect key file for table: ‘...’. Try to repair it. 
  • Can’t open file ‘.MYI’ (errno: 145) 
  • Old database file 
  • Record file is crashed 
  • Can't locate the wtlicensemanager.dll file. 
  • Record was already deleted (or record file crashed) 
 

Причинами появления этих ошибок могут стать:

  • Metadata (Indexes and directories) corruption 
  • Unexpected MySQLD (MySQL Server) shutdown 
  • Malicious software like, virus, worms, etc 
  • Operating system damage 
  • ".myd", “ibdata”, ".frm" and ".myi" file corruption 

По логу и приведенной выше классификации можно определить, что причиной ошибки «Database page corruption on disk or a failed file read» стал сбой из разряда «Metadata (Indexes and directories) corruption», а именно, ошибка в индексе "idx_hash" таблицы "db_test"."test". Там же в логе и подсказан способ решения — это восстановление из дампа, но хочется все таки узнать, что именно в индексе не так, да и восстановление из дампа занимает много времени и откатывает версию данных к той, что была на момент его создания. 

Итак, нам необходимо каким-то образом стартовать сервер БД, и здесь у нас есть как минимум пара вариантов:

  1. Можно воспользоваться методом, предложенным Петром Зайцевым в статье на MySQL Performance Blog. Решение заключается в том, что MySQL перезапускается с параметром innodb_force_recovery отличным от «0», который отменяет различные варианты восстановления InnoDB-таблиц (см. подробнее на http://dev.mysql.com/doc/refman/5.0/en/forcing-innodb-recovery.html), что позволяет поднять MySQL, получить доступ на чтение к данным InnoDB таблиц, и проводить любые манипуляции, но только с MyISAM движком. Таким образом, мы имеем возможность скопировать данные из поврежденной таблицы в ее MyISAM-копию, а далее провести операции удаления поврежденной таблицы, рестарта серввера в обычном режиме (innodb_force_recovery=0) и изменения типа c переименованием новой таблицы. Подробнее смотрите на http://www.mysqlperformanceblog.com/2008/07/04/recovering-innodb-table-corruption/ 
  2. Я же воспользовался другим методом, который спасал нас ранее, при получении ошибки «MySQL got signal 11». На остановленном сервере БД файлы таблицы (.ibd, .frm и, если есть, .trg), обращения к которой происходят с ошибкой (в нашем случае  "db_test"."test") перемещаются в любое другое место, отличное от директории баз данных MySQL (по умолчанию это /var/lib/mysql/). Сервер БД стартует и, не видя подпорченой таблицы, игнорирует записи в логе транзакций, относящиеся к битым данным. Происходит запись в лог информации об успешном старте. Далее проделывается обратная операция, т.е. останавливается MySQL и битая таблица возвращается на место, после чего сервер стартует нормально и уже не пытается восстановить испорченные записи.

Мускуль работает, битая таблица в нем видна, и даже чтение из нее происходит нормально, но любая попытка изменить многострадальную таблицу заканчивается ошибкой «Lost connection to MySQL server during query» и приходится повторять манипуляции из пункта 2. 

Запущенный после поднятия сервера optimize table пересоздал таблицу и провел ее анализ, по результатам которого стало ясно, что ошибка заключается в появлении дублирующей записи в уникальном индексе, удалить которую я не мог, т.к. мускуль  в этом случае опять-таки вылетает с ошибкой. Восстановить данные конечно можно из backup-а, но терять записи, полученные в промежуток с даты последнего дампа - это плохой тон. Поэтому я воспользовался подсказкой Петра (пункт 1) и скопировал битую таблицу, с одной лишь оговоркой: поскольку  MySQL был запущен с параметром  innodb_force_recovery=0, то я работал с движком InnoDB, а не MyISAM и alter table с изменением типа мне уже не требовался. Перезапись оказалась успешной, данные не потерялись и все что осталось сделать — это удалить битую таблицу и переименовать вновь полученную копию.

Резюмируя: решение проблемы восстановления таблицы с битым индексом (по причине «Metadata (Indexes and directories) corruption»), приводящим к появлению ошибки InnoDB:  Database page corruption on disk or a failed file read следующее:

    1. Останавливаем MySQL.
    2. Перемещаем битую таблицу в другое место.
    3. Стартуем мускуль. Не обнаружив битой таблицы, MySQL пропускает этап ее проверки, InnoDB запускается в полнофункциональном режиме.
    4. Останавливаем MySQL снова.
    5. Возвращаем битую таблицу на место.
    6. Стартуем сервер БД (т. к. предыдущий раз сервер был остановлен штатно, в его логах не содержится информации о наличии битой таблицы, требующей восстановления, поэтому InnoDB стартует штатно).
    7. Создаем новую таблицу с точно такой же структурой.
    8. Копируем во вновь созданную таблицу все данные из битой.
    9. Удаляем поврежденную таблицу
    10. Переименовываем вновь получившуюся копию.

Сама по себе операция восстановления продлилась чуть более часа, учитывая малый размер битой таблицы (порядка 14Гб), однако, идентификация проблемы и поиск способа ее решения оказались куда более долгими. Надеюсь, что эта статья поможет вам сократить время поиска.