MySQL - самый быстрый способ ALTER TABLE для InnoDB


12

У меня есть таблица InnoDB, которую я хочу изменить. Таблица содержит ~ 80 миллионов строк и выходит из нескольких индексов.

Я хочу изменить имя одного из столбцов и добавить еще несколько индексов.

  • Какой самый быстрый способ сделать это (при условии, что я могу страдать даже из-за простоя - сервер не используется)?
  • Является ли «простой» alter table, самое быстрое решение?

В настоящее время меня волнует только скорость :)


Пожалуйста SHOW CREATE TABLE tblname\G, покажите столбец, который необходимо изменить, тип данных столбца и новое имя столбца.
RolandoMySQLDBA

вот оно: pastie.org/3078349 столбец, который нужно переименовать, sent_atи добавить к нему еще несколько индексов
Run

sent_at нужно переименовать во что?
RolandoMySQLDBA

скажем так: new_sent_at
пробежал

Ответы:


14

Один верный способ ускорить ALTER TABLE - удалить ненужные индексы

Вот начальные шаги для загрузки новой версии таблицы

CREATE TABLE s_relations_new LIKE s_relations;
#
# Drop Duplicate Indexes
#
ALTER TABLE s_relations_new
    DROP INDEX source_persona_index,
    DROP INDEX target_persona_index,
    DROP INDEX target_persona_relation_type_index
;

Пожалуйста, обратите внимание на следующее:

  • Я удалил source_persona_index, потому что это первый столбец в 4 других индексах

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Я удалил target_persona_index, потому что это первый столбец в 2 других индексах

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Я удалил target_persona_relation_type_index, потому что первые 2 столбца также находятся в target_persona_relation_type_message_id_index

ОК Это заботится о ненужных индексах. Есть ли показатели, которые имеют низкую мощность? Вот способ определить это:

Запустите следующие запросы:

SELECT COUNT(DISTINCT sent_at)               FROM s_relations;
SELECT COUNT(DISTINCT message_id)            FROM s_relations;
SELECT COUNT(DISTINCT target_object_id)      FROM s_relations;

По твоему вопросу там около 80 000 000 строк. Как показывает практика, MySQL Query Optimizer не будет использовать индекс, если количество элементов в выбранных столбцах превышает 5% от числа строк таблицы. В этом случае это будет 4 000 000.

  • Если COUNT(DISTINCT sent_at)> 4 000 000
    • тогда ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Если COUNT(DISTINCT message_id)> 4 000 000
    • тогда ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Если COUNT(DISTINCT target_object_id)> 4 000 000
    • тогда ALTER TABLE s_relations_new DROP INDEX target_object_index;

После определения полезности или бесполезности этих индексов вы можете перезагрузить данные

#
# Change the Column Name
# Load the Table
#
ALTER TABLE s_relations_new CHANGE sent_at sent_at_new int(11) DEFAULT NULL;
INSERT INTO s_relations_new SELECT * FROM s_relations;

Вот так, верно? НЕТ !!!

Если ваш сайт работал все это время, возможно, во время загрузки s_relations_new могут выполняться команды INSERT для s_relations. Как вы можете восстановить эти пропущенные строки?

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

В ОС перезапустите mysql, чтобы никто другой не мог войти в систему, кроме root @ localhost (отключает TCP / IP):

$ service mysql restart --skip-networking

Затем войдите в MySQL и загрузите последние строки:

mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

Затем перезапустите MySQL нормально

$ service mysql restart

Теперь, если вы не можете избавиться от mysql, вам придется использовать s_relations. Просто войдите в MySQL и сделайте следующее:

mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations_old WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

Попробуйте!

ПРЕДОСТЕРЕЖЕНИЕ: Как только вы будете удовлетворены этой операцией, вы можете отказаться от старой таблицы при первой же возможности:

mysql> DROP TABLE s_relations_old;

12

Правильный ответ зависит от версии движка MySQL, который вы используете.

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

Просто используйте ALTER TABLEкак обычно, это будет в основном мгновенно для переименований и удаления индексов, и достаточно быстро для добавления индекса (так же быстро, как и чтение всей таблицы один раз).

Если используется 5.1+, а плагин InnoDB включен, добавление / удаление индексов также будет онлайн. Не уверен насчет переименований.

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

Наконец, пришло время развенчать мифы. К сожалению, мне не хватает кармы, чтобы комментировать ответы, но я чувствую, что важно исправить наиболее проголосовавший ответ. Это неправильно :

Как правило, MySQL Query Optimizer не будет использовать индекс, если мощность выбранных столбцов превышает 5% от числа строк таблицы.

На самом деле все наоборот .

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


Ссылка на документацию плагина InnoDB (не удалось вставить из-за ограничений повторений).
Мезис

2
На MySQL 5.5 я нашел RENAME TABLEмгновенный (как и ожидалось), но CHANGE COLUMNпереименовать первичный ключ сделал полную копию ... 7 часов! Возможно, только потому, что это был первичный ключ? Фигово.
KCD

2

У меня была такая же проблема с Maria DB 10.1.12, затем, прочитав документацию, я обнаружил, что есть возможность выполнить операцию «на месте», которая исключает копию таблицы. С этой опцией таблица изменения очень быстрая. В моем случае это было:

alter table user add column (resettoken varchar(256),
  resettoken_date date, resettoken_count int), algorithm=inplace;

это очень быстро Без опции алгоритма он никогда не закончится.

https://mariadb.com/kb/en/mariadb/alter-table/


0

Для переименования столбца

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

должно быть хорошо и не иметь никакого простоя.

Для индексов инструкция CREATE INDEX заблокирует таблицу. Если это неиспользованный раб, как вы упомянули, это не проблема.

Еще один вариант - создать новую таблицу с правильными именами столбцов и индексами. Затем вы можете скопировать все данные в него, а затем выполнить серию

BEGIN TRAN;
ALTER TABLE RENAME tablename tablenameold;
ALTER TABLE RENAME newtablename tablename;
DROP TABLE tablenameold;
COMMIT TRAN;

Это минимизирует время простоя за счет временного использования в два раза больше места.


1
DDL в MySQL не является транзакционным. Каждый оператор DDL запускает COMMIT. Я писал об этом: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA

0

У меня тоже есть эта проблема, и я использовал этот SQL:

/*on créé la table COPY SANS les nouveaux champs et SANS les FKs */
CREATE TABLE IF NOT EXISTS prestations_copy LIKE prestations;

/* on supprime les FKs de la table actuelle */
ALTER TABLE `prestations`
DROP FOREIGN KEY `fk_prestations_pres_promos`,
DROP FOREIGN KEY `fk_prestations_activites`;

/* on remet les FKs sur la table copy */
ALTER TABLE prestations_copy 
    ADD CONSTRAINT `fk_prestations_activites` FOREIGN KEY (`act_id`) REFERENCES `activites` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
    ADD CONSTRAINT `fk_prestations_pres_promos` FOREIGN KEY (`presp_id`) REFERENCES `pres_promos` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION;

/* On fait le transfert des données de la table actuelle vers la copy, ATTENTION: il faut le même nombre de colonnes */
INSERT INTO prestations_copy
SELECT * FROM prestations;

/* On modifie notre table copy de la façon que l'on souhaite */
ALTER TABLE `prestations_copy`
    ADD COLUMN `seo_mot_clef` VARCHAR(50) NULL;

/* on supprime la table actuelle et renome la copy avec le bon nom de table */
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE prestations;
RENAME TABLE prestations_copy TO prestations;
SET FOREIGN_KEY_CHECKS=1;   

Я надеюсь, что это может помочь кому-то

С Уважением,

Будет

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.