MySQL - найшвидший спосіб змінити таблицю для InnoDB


12

У мене є таблиця InnoDB, яку я хочу змінити. У таблиці розміщено ~ 80М рядків і закрийте кілька індексів.

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

  • Який найшвидший спосіб це зробити (якщо припустити, що я можу зазнати навіть простоїв - сервер є невідомим рабом)?
  • Це "звичайне" alter table, найшвидше рішення?

В цей час мене все хвилює швидкість :)


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

ось це: pastie.org/3078349 стовпець, який потрібно перейменувати, є sent_atі додати до нього ще кілька індексів
Ran

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 інших індексах

    • унікальний_target_persona
    • унікальний_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Я скинув target_persona_index, оскільки це перший стовпець у двох інших індексах

    • 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 не буде використовувати індекс, якщо кардинальність вибраних стовпців перевищує 5% від кількості рядків таблиці. У цьому випадку це було б 4 мільйони.

  • Якщо 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;

Спробувати !!!

CAVEAT: Після того, як ви будете задоволені цією операцією, ви можете кинути стару таблицю якнайшвидше:

mysql> DROP TABLE s_relations_old;

12

Правильна відповідь залежить від версії механізму MySQL, який ви використовуєте.

Якщо використовується 5.6+, перейменування та додавання / видалення індексів виконуються в Інтернеті , тобто без копіювання всіх даних таблиці.

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

Якщо використовується 5.1+ і плагін InnoDB увімкнено, додавання / видалення індексів також буде в Інтернеті. Не впевнений у перейменах.

Якщо ви використовуєте старішу версію, ALTER TABLEвона все ще є найшвидшою, але, ймовірно, буде жахливо повільною, оскільки всі ваші дані будуть знову вставлені до тимчасової таблиці під кришкою.

Нарешті, час для розвінчання міфу. На жаль, у мене немає достатньої кількості карми, щоб коментувати відповіді, але я вважаю, що важливо виправити найбільш голосовану відповідь. Це неправильно :

Як правило, оптимізатор запитів MySQL не буде використовувати індекс, якщо кардинальність вибраних стовпців перевищує 5% від кількості рядків таблиці

Це насправді навпаки .

Індекси корисні для вибору декількох рядків, тому важливо, щоб вони мали високу кардинальність, що означає багато чітких значень і статистично мало рядків з однаковим значенням.


Посилання на документацію щодо плагінів InnoDB (не вдалося вставити через обмеження повтору).
мезіс

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

2

У мене була така ж проблема з Марією БД 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.