Обновление Oracle MySQL с 5.7 до 8.0

Буквально 4 дня назад вышла первая стабильная версия Oracle MySQL 8.0.11
Краткий список изменений на русском языке можно прочитать здесь, а полный на английском языке на официальном сайте здесь.

Давайте попробуем обновить Oracle MySQL 5.7.21 до 8.0.11

Исходные данные:
ОС: Oracle Linux Server release 7.1
БД: Oracle MySQL 5.7.21
Задача: Обновиться до версии 8.0.11

Перед тем как обновиться нужно в обязательном порядке посмотреть все используемые настройки из /etc/my.cnf и попытаться понять какие из них в новом релизе 8.0.11 стали неподдерживаемыми (deprecated) и могут помешать запустить наш экземпляр MySQL после обновления, для этого перед обновлением нам стоит прочитать официальную документацию и дополнительно еще эту страницу.
Так же перед обновлением стоит прочитать официальную документацию по стратегии обновления. Ниже я пройдусь по всем пунктам стратегии обновления и поясню их суть.

Так же перед обновлением Вы должны проверить ваши приложения, работающие в БД на предмет совместимости с новой версией.

Например в MySQL 8.0 были добавлены в список зарезервированных некоторые ключевые слова, к примеру слово GROUPS, которые нельзя использовать в запросах без указания кавычек экранирования.

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

Я провел соответствующие тесты и мои приложения готовы к новой версии MySQL.

Теперь можно приступить к проверка всех БД и выполнения ряда подготовительных действий.

1. Проверка всех БД на соответствие требованиям MySQL 8.0 и конвертация таблиц в InnoDB

Первым делом нужно запустить процедуру проверки всех БД для поиска проблемных участков, это самый первый пункт в стратегии обновления:

# mysqlcheck -u root -p --all-databases --check-upgrade

Результатом должно быть OK по всем БД и таблицам в них.

Далее, мы предполагаем, что у нас экземпляр MySQL без репликации.

Так же у Вас не должно быть секционированных таблиц, в которых используется механизм хранения, который не имеет встроенной поддержки разбиения на разделы. Чтобы идентифицировать такие таблицы, выполните этот запрос:

SELECT TABLE_SCHEMA, TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE ENGINE NOT IN ('innodb', 'ndbcluster')
AND CREATE_OPTIONS LIKE '%partitioned%';

Любая таблица, указанная в запросе, должна быть изменена для использования движка InnoDB или быть не секционированной. Чтобы изменить механизм хранения таблиц в InnoDB, нужно выполнить запрос вида:

ALTER TABLE table_name ENGINE = INNODB;

Чтобы сделать удалить секционирование у таблицы, выполните запрос вида:

ALTER TABLE table_name REMOVE PARTITIONING;

Вообще выход MySQL 8.0 ознаменовал закат хранилища MyISAM, поэтому оставаться на нем я не вижу смысла и перед обновлением до 8.0 нам нужно конвертировать движок всех таблиц в InnoDB.

Для начала оценим фронт работ с помощью запроса:

SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES as T WHERE T.ENGINE NOT IN ('innodb', 'ndbcluster') AND T.TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql');

Если запрос показал 0 строк, то Вам повезло и у Вас нет ни одной таблицы с движком отличным от InnoDB или NDBCluster.
Если запрос выдал Вам список таблиц, то нужно провести их конвертацию.

Для удобства конвертации составим запросы на конвертацию отдельно для каждой базы, например возьмем БД с именем MYDB:

mysql -u root -p -N -e "SELECT CONCAT('ALTER TABLE ',TABLE_SCHEMA,'.',TABLE_NAME,' ENGINE=InnoDB;') EXEC FROM INFORMATION_SCHEMA.TABLES as T WHERE T.ENGINE='MyISAM' AND T.TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND T.TABLE_SCHEMA = 'MYDB' ORDER BY TABLE_ROWS asc;" > MYDB.sql

У нас получился файл MYDB.sql в котором перечислены команды и все нужные таблицы из БД для конвертации, внешне он выглядит так:

ALTER TABLE MYDB.table_1 ENGINE=InnoDB;
ALTER TABLE MYDB.table_2 ENGINE=InnoDB;

Что нужно знать перед конвертацией таблиц:
1. Конвертация может идти долго в зависимости от размера ваших таблиц, в моем запросе самыми последними в списке на конвертацию буду самые большие таблицы;
2. Таблицы с движком InnoDB занимают в 1,5 раза больше места на HDD, так что проверьте есть ли у Вас столько свободного пространства.
3. Прочитайте мою статью «MySQL и ошибка Index column size too large. The maximum column size is 767 bytes.» и выполните соответствующие настройки по избежании появления подобного рода ошибок. Если Вы используете последнюю версию MySQL 5.7.x то с высокой долей вероятности Вас эти ошибки не затронут.
4. Рекомендую конвертировать все ваши БД и таблицы в кодировку utf8mb4, но это не обязательное условие, хотя в MySQL 8.0 кодировкой по умолчанию стала utf8mb4, это нужно знать и учитывать при обновлении.

Теперь нужно остановить все приложения работающие с нашей БД и запустить конвертацию таблиц:

# mysql -u root -p MYDB
mysql> source MYDB.sql

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

Двигаемся дальше. Т.к. в MySQL 8.0 был представлен новый механизм хранения системных данных (Transactional Data Dictionary), то перед переходом на MySQL 8.0 мы должны убедиться, что в системной БД mysql нет таблиц с пересекающимися именами из словаря (Data Dictionary), это делается запросом:

SELECT TABLE_SCHEMA, TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE LOWER(TABLE_SCHEMA) = 'mysql'
and LOWER(TABLE_NAME) IN
(
'catalogs',
'character_sets',
'collations',
'column_statistics',
'column_type_elements',
'columns',
'dd_properties',
'events',
'foreign_key_column_usage',
'foreign_keys',
'index_column_usage',
'index_partitions',
'index_stats',
'indexes',
'parameter_type_elements',
'parameters',
'resource_groups',
'routines',
'schemata',
'st_spatial_reference_systems',
'table_partition_values',
'table_partitions',
'table_stats',
'tables',
'tablespace_files',
'tablespaces',
'triggers',
'view_routine_usage',
'view_table_usage'
);

Он должен вернуть 0 строк, тогда все хорошо. В противном случае Вы не сможете обновить свой MySQL до версии 8.0 не устранив эту проблему. Для устранения проблемы нужно переименовать проблемные таблицы с помощью RENAME TABLE.

Следующие требование — длина имени внешнего ключа (foreign key) не должна превышать 64 символа.
Чтобы определить таблицы с слишком длинными именами, выполните запрос:

SELECT TABLE_SCHEMA, TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME IN
  (SELECT LEFT(SUBSTR(ID,INSTR(ID,'/')+1),
               INSTR(SUBSTR(ID,INSTR(ID,'/')+1),'_ibfk_')-1)
   FROM INFORMATION_SCHEMA.INNODB_SYS_FOREIGN
   WHERE LENGTH(SUBSTR(ID,INSTR(ID,'/')+1))>64);

Все найденные таблицы должны быть приведены в соответствие этого требования. Для этого используйте ALTER TABLE.

И последнее что нужно знать — это то, что обновляемый вами экземпляр MySQL 5.7.x не должен использовать функции которые более не поддерживаются в MySQL 8.0.x:

а) Таблицы, в которых используется механизм хранения, не поддерживаемый в MySQL 8.0, должны быть изменены, чтобы использовать поддерживаемый движок. Например, MySQL 8.0 еще не поддерживает MySQL Cluster, поэтому таблицы NDB должны быть изменены для использования другого механизма хранения.

б) В MySQL 8.0 были удалены некоторые параметры запуска сервера и системные переменные. См. Функции, удаленные в MySQL 8.0 и Раздел 1.5, «Переменные и параметры сервера и состояния, добавленные, устаревшие или удаленные в MySQL 8.0».

Указания на эти разделы я писал в самом начале статьи.

Применительно к моему файлу конфигурации /etc/my.cnf перед обновлениям я удалил следующие настройки:

query_cache_type
query_cache_limit
query_cache_size

В MySQL 8.0 кэш запросов (query cache) удалили как таковой, так что эти опции и еще несколько более не поддерживаются. Так же более не поддерживаются модификаторы SQL_CACHE и SQL_NO_CACHE в SELECT запросах и статусные переменные Qcache_XXXX.

Так же я удалил настройку log_warnings которая так же не поддерживается, вместо неё используется log_error_verbosity. После обновления я добавлю настройку log_error_verbosity.

Забегая вперед скажу, что так же я закомментировал опцию expire_logs_days, после обновления её нужно заменить на binlog_expire_logs_seconds
Раньше expire_logs_days указывала через какое количество дней бинарный журнал будет удалятся, теперь с помощью binlog_expire_logs_seconds это можно сделать с точностью до секунд.

Так же я использовал плагин validate_password для контроля качества паролей, в версии 8.0 он был заменен отдельным компонентом, поэтому перед обновлением MySQL я закомментировал следующие настройки в /etc/my.cnf:

validate-password=FORCE_PLUS_PERMANENT
validate_password_special_char_count=0

У вас могут быть другие опции и настройки, поэтому процедура обновления может быть более сложной.

Теперь можно приступить к обновлению.

2. Добавляем новый репозитарий в Oracle Linux

Посмотрим список включенных репозитариев:

# yum repolist enabled | grep mysql
mysql-connectors-community MySQL Connectors Community                         49
mysql-tools-community      MySQL Tools Community                              61
mysql57-community          MySQL 5.7 Community Server                        255

Добавим новый репозитарий, есть 2 варианта:

Вариант 1 — мы оставляем старые репозитарии для MySQL 5.7 и описываем новый репозитарий в файле /etc/yum.repos.d/mysql-community.repo

# cat <<EOT >> /etc/yum.repos.d/mysql-community.repo
[mysql80-community]
name=MySQL 8.0 Community Server
baseurl=http://repo.mysql.com/yum/mysql-8.0-community/el/7/\$basearch/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql
EOT

Вариант 2 — удаляем пакет со старыми репозитариями для MySQL 5.7 и устанавливаем RPM-пакет, который по сути сделает тоже самое:

# yum remove mysql57-community-release
# yum install https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm -y

Я поступил по второму варианту, он более правильный.

Если Вы поступили по Варианту 1, то нужно будет отключить репозитарий версии MySQL 5.7:

# yum-config-manager --disable mysql57-community

Проверим список активных репозитариев:

# yum repolist enabled | grep mysql
mysql-connectors-community/x86_64 MySQL Connectors Community                  51
mysql-tools-community/x86_64      MySQL Tools Community                       63
mysql80-community/x86_64          MySQL 8.0 Community Server                  17

Отлично, mysql80-community есть в списке.

Проверим доступные обновления:

# yum makecache fast
# yum check-update

В списки на обновление должны присутствовать пакеты:

mysql-community-client.x86_64                          8.0.11-1.el7                                        mysql80-community
mysql-community-common.x86_64                          8.0.11-1.el7                                        mysql80-community
mysql-community-libs.x86_64                            8.0.11-1.el7                                        mysql80-community
mysql-community-libs-compat.x86_64                     8.0.11-1.el7                                        mysql80-community
mysql-community-server.x86_64                          8.0.11-1.el7                                        mysql80-community

3. Обновление MySQL

Посмотрим список установленных пакетов:

# yum list installed | grep "^mysql"
mysql-community-client.x86_64      5.7.21-1.el7                      @mysql57-community
mysql-community-common.x86_64      5.7.21-1.el7                      @mysql57-community
mysql-community-libs.x86_64        5.7.21-1.el7                      @mysql57-community
mysql-community-libs-compat.x86_64 5.7.21-1.el7                      @mysql57-community
mysql-community-server.x86_64      5.7.21-1.el6                      @mysql57-community
mysql80-community-release.noarch

Выясним местоположение лога ошибок mysql:

# cat /etc/my.cnf | egrep '^log-error|^log_error'
log_error               = /var/log/mysql/error.log

Анализ данного лога очень нам поможет при запуске новой версии MySQL, поэтому в отдельном окне консоли запустим tail:

# tail -f /var/log/mysql/error.log

Теперь выполним обновление пакета mysql-community-server:

# yum update mysql-community-server

Нам предложат обновить ряд пакетов (mysql-community-server, mysql-community-client, mysql-community-common, mysql-community-libs) до версии 8.0.11, соглашаемся. (Будет скачано порядка 370 MB)

Далее по логу мы увидим, что наш MySQL 5.7.21 будет остановлен и запущен уже 8.0.11, но с некоторыми ошибками, которые нам предстоит исправить. Лог я привожу ниже.

.....
2018-04-23T05:34:44.984167Z 0 [Note] /usr/sbin/mysqld: Shutdown complete

2018-04-23T05:34:46.475828Z 0 [Warning] [MY-011070] [Server] 'Disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. Consider not using this option as it' is deprecated and will be removed in a future release.
2018-04-23T05:34:46.481621Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.11) starting as process 4925
2018-04-23T05:34:54.215467Z 0 [Warning] [MY-013129] [Server] A message intended for a client cannot be sent there as no client-session is attached. Therefore, we're sending the information to the error-log instead: MY-001287 - 'validate password plugin' is deprecated and will be removed in a future release. Please use validate_password component instead
2018-04-23T05:35:14.414991Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2018-04-23T05:35:14.474844Z 0 [ERROR] [MY-013129] [Server] A message intended for a client cannot be sent there as no client-session is attached. Therefore, we're sending the information to the error-log instead: MY-001146 - Table 'mysql.component' doesn't exist
2018-04-23T05:35:14.474953Z 0 [Warning] [MY-013129] [Server] A message intended for a client cannot be sent there as no client-session is attached. Therefore, we're sending the information to the error-log instead: MY-003543 - The mysql.component table is missing or has an incorrect definition.
2018-04-23T05:35:14.488186Z 0 [Warning] [MY-010929] [Server] Storage engine 'MyISAM' does not support system tables. [mysql.user].
2018-04-23T05:35:14.488241Z 0 [Warning] [MY-010929] [Server] Storage engine 'MyISAM' does not support system tables. [mysql.db].
2018-04-23T05:35:14.488251Z 0 [Warning] [MY-010929] [Server] Storage engine 'MyISAM' does not support system tables. [mysql.tables_priv].
2018-04-23T05:35:14.488260Z 0 [Warning] [MY-010929] [Server] Storage engine 'MyISAM' does not support system tables. [mysql.columns_priv].
2018-04-23T05:35:14.488268Z 0 [Warning] [MY-010929] [Server] Storage engine 'MyISAM' does not support system tables. [mysql.procs_priv].
2018-04-23T05:35:14.488276Z 0 [Warning] [MY-010929] [Server] Storage engine 'MyISAM' does not support system tables. [mysql.proxies_priv].
2018-04-23T05:35:14.489419Z 0 [ERROR] [MY-013143] [Server] Column count of mysql.user is wrong. Expected 49, found 45. The table is probably corrupted
2018-04-23T05:35:14.489480Z 0 [Warning] [MY-010966] [Server] ACL table mysql.role_edges missing. Some operations may fail.
2018-04-23T05:35:14.489490Z 0 [Warning] [MY-010966] [Server] ACL table mysql.default_roles missing. Some operations may fail.
2018-04-23T05:35:14.489498Z 0 [Warning] [MY-010966] [Server] ACL table mysql.global_grants missing. Some operations may fail.
2018-04-23T05:35:14.489506Z 0 [Warning] [MY-010966] [Server] ACL table mysql.password_history missing. Some operations may fail.
2018-04-23T05:35:14.492335Z 0 [ERROR] [MY-010965] [Server] Missing system table mysql.global_grants; please run mysql_upgrade to create it.
2018-04-23T05:35:14.501952Z 0 [Warning] [MY-010727] [Server] System table 'func' is expected to be transactional.
2018-04-23T05:35:14.524571Z 0 [Warning] [MY-010405] [Repl] Info table is not ready to be used. Table 'mysql.slave_master_info' cannot be opened.
2018-04-23T05:35:14.524641Z 0 [ERROR] [MY-010422] [Repl] Error in checking mysql.slave_master_info repository info type of TABLE.
2018-04-23T05:35:14.524662Z 0 [ERROR] [MY-010415] [Repl] Error creating master info: Error checking repositories.
2018-04-23T05:35:14.524674Z 0 [ERROR] [MY-010426] [Repl] Slave: Failed to initialize the master info structure for channel ''; its record may still be present in 'mysql.slave_master_info' table, consider deleting it.
2018-04-23T05:35:14.524687Z 0 [ERROR] [MY-010529] [Repl] Failed to create or recover replication info repositories.
2018-04-23T05:35:14.528315Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.11'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  MySQL Community Server - GPL.

Самое важное — это последняя строка, которая говорит нам, что MySQL 8.0.11 был запущен, это очень важно. Если в процессе запуска будут критические ошибки, например в файле конфигурации будут найдены опции которые уже не существуют в новой версии, то MySQL не запустится и выдаст соответствующие ошибки в лог.

Большинство ошибок в логе при первом старте MySQL 8 связан с тем, что мы не обновили системные БД (mysql и sys), давайте сделаем это запустив программу обновления mysql_upgrade:

# mysql_upgrade -u root -p

В процессе обновления должен будет выдан OK по всем базам и таблицам. Если по каким то БД и таблицам будет выдана ошибка обновления, то Вам стоит на них обратить пристальное внимание. В логе error.log в процессе обновления мы увидим ряд предупреждений и возможно даже ошибок — это вполне нормально.

Теперь перезапустим MySQL 8:

# systemctl restart mysqld

Лог ошибок стал значительно меньше, теперь можно устранить те небольшие ошибки, что у нас остались.

4. Дополнительные действия после обновления (исправление ошибок)

Первым предупреждением в логе идет «‘Disabling symbolic links using —skip-symbolic-links (or equivalent) is the default. Consider not using this option as it’ is deprecated and will be removed in a future release.»
Данная опция помечена как deprecated и в будущих релизах будет удалена совсем, поэтому мы должны ее удалить из /etc/my.cnf
Удаляем строку symbolic-links=0 из /etc/my.cnf

Следующее предупреждение «A message intended for a client cannot be sent there as no client-session is attached. Therefore, we’re sending the information to the error-log instead: MY-001287 — ‘validate password plugin’ is deprecated and will be removed in a future release. Please use validate_password component instead»
Оно говорит о том, что плагин validate_password был заменен компонентом и в дальнейшем как плагин будет удален, поэтому мы должны удалить плагин validate_password и активировать новый компонент, выполняем:

Отключим плагин validate_password выполнив команду:

# mysql -u root -p -e "UNINSTALL PLUGIN validate_password;"

Далее взамен плагина validate_password установим его замену в виде компонента:

# mysql -u root -p -e "INSTALL COMPONENT 'file://component_validate_password';"
# mysql -u root -p -e "SET GLOBAL validate_password.special_char_count = 0;"

Вместо настройки validate_password_special_char_count в MySQL 8.0 нужно использовать validate_password.special_char_count, выполним замену в фале /etc/mysql.cnf в секции [mysqld]

validate_password.special_char_count = 0

И последний перезапуск MySQL 8:

# systemctl restart mysqld

На этот раз в логе ошибок (см. ниже) лишь 1 незначительное предупреждение связанное с самоподписным SSL сертификатом, его можно игнорировать.

2018-04-23T06:05:48.758044Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.11)  MySQL Community Server - GPL.
2018-04-23T06:05:49.347784Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.11) starting as process 5301
2018-04-23T06:05:51.203327Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2018-04-23T06:05:51.333792Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.11'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  MySQL Community Server - GPL.

У Вас могут быть дополнительные предупреждения если к примеру в /etc/my.cnf прописаны опции: skip-external-locking и skip-name-resolve

На этом обновление MySQL 5.7.21 до версии 8.0.11 можно считать законченным.

До скорых встреч. Если у Вас возникли вопросы или Вы хотите чтобы я помог Вам, то Вы всегда можете связаться со мной разными доступными способами.