web-dev-qa-db-ja.com

MySQL-InnoDBのALTER TABLEへの最速の方法

変更したいInnoDBテーブルがあります。テーブルには〜8千万行あり、いくつかのインデックスを終了します。

列の1つの名前を変更し、さらにいくつかのインデックスを追加したいと思います。

  • それを行う最も速い方法は何ですか(私がダウンタイムさえも被る可能性があると仮定します-サーバーは未使用のスレーブです)?
  • 「プレーン」ですalter table、最速のソリューションですか?

この時点で、私が気にしているのは速度です:)

12
Ran

ALTER TABLEを高速化する確実な方法の1つは、不要なインデックスを削除することです

新しいバージョンのテーブルをロードするための最初のステップは次のとおりです

_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
  • 最初の2列もtarget_persona_relation_type_message_id_indexにあるため、target_persona_relation_type_indexを削除しました

OK不要なインデックスを処理します。カーディナリティが低いインデックスはありますか?これを確認する方法は次のとおりです。

次のクエリを実行します。

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

それだけですよね?いいえ!!!

Webサイトがずっと稼働している場合、s_relations_newのロード中にs_relationsに対してINSERTが実行されている可能性があります。欠けている行をどのように取得できますか?

S_relations_newで最大IDを見つけて、s_relationsからそのIDの後にすべてを追加します。テーブルが確実に凍結され、この更新のみに使用されるようにするには、s_relation_newに挿入された最後の行を取得するために、少しのダウンタイムが必要です。ここではあなたが何をすべきかです:

OSで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;
_
14
RolandoMySQLDBA

正しい答えは、使用しているMySQLエンジンのバージョンによって異なります。

5.6+を使用している場合、名前の変更とインデックスの追加/削除は online で実行されます。つまり、すべてのテーブルのデータをコピーすることはありません。

ALTER TABLE通常どおり、ほとんどの場合、名前の変更とインデックスの削除は瞬時に行われ、インデックスの追加はかなり高速になります(すべてのテーブルを一度に読み取るのと同じ速さ)。

5.1+を使用していて、InnoDBプラグインが有効になっている場合は、インデックスの追加/削除もオンラインになります。名前の変更については不明です。

古いバージョンを使用している場合、ALTER TABLEは依然として最速ですが、すべてのデータが内部の一時テーブルに再挿入されるため、恐ろしく遅くなります。

最後に、神話を暴く時間です。残念ながら、回答にコメントするのに十分なカルマはありませんが、最も投票された回答を修正することが重要だと感じています。これは間違っています

経験則として、MySQL Query Optimizerは、選択された列のカーディナリティがテーブル行カウントの5%より大きい場合、インデックスを使用しません

実際には 他の方法 です。

インデックスはfew行を選択するのに役立ちます。したがって、それらがhighカーディナリティを持っていることが重要です。同じ値。

12
mezis

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/

2
Saule

列の名前を変更するには、

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

正常であり、ダウンタイムは発生しません。

インデックスの場合、CREATE INDEXステートメントはテーブルをロックします。あなたが言ったようにそれが未使用のスレーブであるなら、それは問題ではありません。

他の1つのオプションは、適切な列名とインデックスを持つ真新しいテーブルを作成することです。次に、すべてのデータをそこにコピーしてから、一連の

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

これにより、一時的に2倍のスペースを使用することを犠牲にして、ダウンタイムを最小限に抑えることができます。

0
TetonSig

私にもこの問題があり、次の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;   

私はそれが誰かを助けることを願っています

よろしく、

意志

0
William Rossier