it-swarm-eu.dev

MySQL - nejrychlejší způsob, jak ALTER TABLE pro InnoDB

Mám tabulku InnoDB, kterou chci změnit. Tabulka má ~ 80 milionů řádků a ukončuje několik indexů.

Chci změnit název jednoho ze sloupců a přidat několik dalších ukazatelů.

  • Jaký je nejrychlejší způsob, jak toho dosáhnout (za předpokladu, že bych mohl trpět i prostoje - server je nevyužitý otrok)?
  • Je "prostý" alter table, nejrychlejší řešení?

V tuto chvíli mi záleží jen na rychlosti :)

12
Ran

Jeden jistý způsob, jak zrychlit ALTER TABLE, je odstranit zbytečné indexy

Zde jsou úvodní kroky k načtení nové verze tabulky

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
;

Vezměte prosím na vědomí následující:

  • Vynechal jsem source_persona_index, protože je to první sloupec ve 4 dalších indexech

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • Vynechal jsem target_persona_index, protože je to první sloupec ve 2 dalších indexech

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • Vynechal jsem target_persona_relation_type_index, protože první 2 sloupce jsou také v target_persona_relation_type_message_id_index

OK To se stará o zbytečné indexy. Existují nějaké indexy, které mají nízkou kardinálnost? Zde je způsob, jak zjistit, že:

Spusťte následující dotazy:

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;

Podle vaší otázky existuje asi 80 000 000 řádků. Obecně lze říci, že nástroj MySQL Query Optimizer nepoužívá index, pokud je mohutnost vybraných sloupců větší než 5% počtu řádků tabulky. V tomto případě by to bylo 4 000 000.

  • Pokud COUNT(DISTINCT sent_at)> 4 000 000
    • pak ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • Pokud COUNT(DISTINCT message_id)> 4 000 000
    • pak ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • Pokud COUNT(DISTINCT target_object_id)> 4 000 000
    • pak ALTER TABLE s_relations_new DROP INDEX target_object_index;

Jakmile je stanovena užitečnost nebo neužitečnost těchto indexů, můžete data znovu načíst

#
# 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;

To je ono, že? ANI NÁHODOU !!!

Pokud váš web byl celou dobu provozován, může se stát, že během načítání s_relations_new budou spuštěny INSERTy proti s_relations. Jak můžete načíst ty chybějící řádky?

Vyhledejte maximální ID v s_relations_new a připojte vše za toto ID z s_relations. Aby bylo zajištěno, že tabulka je zamrzlá a používá se pouze pro tuto aktualizaci, musíte mít trochu prostoje kvůli získání těch posledních řádků, které byly vloženy do s_relation_new. Zde je to, co děláte:

V OS restartujte mysql, aby se nikdo jiný nemohl přihlásit, ale root @ localhost (zakáže TCP/IP):

$ service mysql restart --skip-networking

Dále se přihlaste k mysql a načtěte poslední řádky:

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;

Poté normálně restartujte mysql

$ service mysql restart

Nyní, pokud nemůžete sundat mysql, budete muset udělat návnadu a přepnout na s_relations. Stačí se přihlásit do mysql a udělat následující:

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;

Pokusit se !!!

CAVEAT: Jakmile budete s touto operací spokojeni, můžete starý stůl co nejdříve zrušit:

mysql> DROP TABLE s_relations_old;
14
RolandoMySQLDBA

Správná odpověď závisí na verzi MySQL enginu, který používáte.

Pokud používáte 5,6+, přejmenování a přidávání/odebírání indexů se provádí online , tj. Bez kopírování všech dat v tabulce.

Stačí použít ALTER TABLE jako obvykle, bude to většinou okamžité pro přejmenování a poklesy indexů a přiměřeně rychlé pro přidání indexu (tak rychlé, jako je čtení všech tabulek jednou).

Pokud používáte 5.1+ a je povolen plugin InnoDB, přidávání/odebírání indexů bude také online. Nejste si jisti jmény.

Pokud používáte starší verzi, ALTER TABLE je stále nejrychlejší - ale pravděpodobně bude strašně pomalé, protože všechna vaše data bude znovu vložena do dočasné tabulky pod kapotou.

Konečně, čas na mýtus odhalení. Bohužel tu nemám dost karmy, abych mohl komentovat odpovědi, ale mám pocit, že je důležité opravit nejvíce hlasovanou odpověď. To je špatné :

Obecně lze říci, že nástroj MySQL Query Optimizer nepoužívá index, pokud je mohutnost vybraných sloupců větší než 5% počtu řádků v tabulce.

Je to vlastně naopak .

Indexy jsou užitečné pro výběr málo řádků, takže je důležité, aby měly vysoký mohutnost, což znamená mnoho odlišných hodnot a statisticky málo řádků se stejnou hodnotou.

12
mezis

Stejný problém jsem měl s Maria DB 10.1.12, poté jsem si po přečtení dokumentace zjistil, že existuje možnost provést operaci „na místě“, která vylučuje kopii tabulky. S touto možností je alternativní tabulka velmi rychlá. V mém případě to bylo:

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

to je velmi rychlé. Bez možnosti algoritmu by to nikdy neskončilo.

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

2
Saule

Pro přejmenování sloupce

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

by měl být v pořádku a neměl by mít žádné prostoje.

Pro indexy zamkne tabulku příkaz CREATE INDEX. Pokud je to nevyužitý otrok, jak jste zmínil, není to problém.

Další možností by bylo vytvoření zcela nové tabulky, která bude mít správné názvy sloupců a indexy. Pak byste do něj mohli zkopírovat všechna data a poté provést řadu

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

To by minimalizovalo prostoje za cenu dočasného použití dvojnásobku místa.

0
TetonSig

Mám také tento problém a použil jsem tento 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;   

Doufám, že to někomu může pomoci

Pozdravy,

Vůle

0
William Rossier