VillageSQL is a drop-in replacement for MySQL with extensions.
All examples on this page work on VillageSQL. Install Now →
ALTER TABLE を実行するとき、1 つの疑問が生じます。このステートメントは、実行中にアプリケーションをブロックするのか? このガイドはそれを、実際のテーブルで、並行する接続にずっと観察させながら直接測定します。MySQL のスキーマ移行は、マイグレーションファイルの整理とバージョン管理の方法を扱います。このガイドは、そのマイグレーションファイルの 1 つが稼働中のテーブルに対して実行されている間に、そのテーブルで実際に何が起きるかを扱います。
テストのセットアップ
このガイドのすべての数値は同じテーブルから得たものです。ローカルの VillageSQL サーバー上の、3,276,802 行(168.7 MB のデータ)をロードしたorders テーブルです。
INSERT ... SELECT で数回倍にして数百万行に到達させました。正確なサイズは以下の結論には影響しませんが、テーブル全体の再構築がコンマ数秒より長くかかる程度にテーブルが大きい必要があります。そうでなければ、それと並行して実行されるものを何も観察できません。
オンラインの ALTER はトラフィックをブロックしない
上の 327 万行のテーブルにALGORITHM=INPLACE, LOCK=NONE でセカンダリインデックスを追加します。
SHOW PROCESSLIST は、それを本物の進行中の操作として示しました。
information_schema.INNODB_TRX は、同じテーブルに対する別の ADD INDEX から、同じステートメントを実行中のトランザクションとして示します。
ALTER がまだ altering table 状態にある間に開いて実行した 3 つ目の接続から、同じテーブルに対する INSERT と SELECT はどちらもすぐに完了しました。
ALTER の完了を待ちませんでした。これこそが LOCK=NONE の要点です。テーブルの再構築は、並行する変更を取り込む InnoDB のオンライン DDL ログを使ってバックグラウンドで行われ、テーブルに対する DML は、再構築が追いついて入れ替わるまで、元のデータ構造に対して進みます。
ALGORITHM=INSTANT と ALGORITHM=INPLACE
すべてのオンライン操作が同じコストというわけではありません。ALGORITHM=INSTANT でテーブルの末尾に列を追加すると、テーブルのメタデータが変わるだけです。1 行も読み取らず、書き換えません。
ALGORITHM=INPLACE で強制すると次のようになります。
INSTANT は INPLACE のおよそ 20 倍速くなりました。INPLACE はテーブルのデータディクショナリのエントリに触れ、並行する DML と調整する必要があります。INSTANT はどちらもしません。その差はテーブルサイズとともに広がります。INSTANT は行数に関係なく一定のままですが、INPLACE はそれに比例します。
INSTANT はすべての操作に使えるわけではありません。BLOB/TEXT のデフォルト値を持つ列の追加や、既存の列の型の変更は、INPLACE または COPY にフォールバックします。テーブルの途中を含む任意の位置への列の追加も INSTANT を使います(MySQL 8.0.29 以降)。
オンライン DDL が役に立たない場合: 列の型変更
すべてのALTER TABLE がロックを避けられるわけではありません。列のデータ型を変更するには、MySQL がすべての行を書き換える必要があり、INPLACE にはそれができません。
ALGORITHM=INSTANT を要求しても、別の理由で同じように失敗します。
COPY で、これはテーブル全体を再構築し、その間、排他ロックを取得します。同じテーブルに対してそれを実行し、再構築中に 2 つ目の接続から行を挿入すると、違いが直接示されます。
ALTER の開始から 0.3 秒後に発行した並行する INSERT は、ALTER がほぼ終わるまで返ってきませんでした。
SHOW PROCESSLIST は、ALTER を copy to tmp table 状態で、ブロックされた INSERT をその横に Waiting for table metadata lock 状態で示しました。この状態が診断の手がかりです。詰まっているステートメントを名指しし、それが遅く実行されているのではなくロックの後ろで待っていることを教えてくれます。この同じステートメントに LOCK=NONE を付けて早期に失敗させてみることこそが、本番テーブルに対して実行する前に、それが書き換えにかかる時間だけ書き込み側をブロックすることを知る方法です。
外部キーとオンライン DDL
外部キーは、ALGORITHM と LOCK とは無関係の制限を追加します。FOREIGN KEY (order_id) REFERENCES orders(id) を持つ子テーブル order_items では、通常の ADD COLUMN は外部キーがあっても ALGORITHM=INSTANT でミリ秒単位で完了します。しかし、2 つの操作はアルゴリズムに関係なく完全に失敗します。
外部キーが依存する親の主キーを削除する場合です。
オンライン DDL が安全に行えない変更のための gh-ost
上のMODIFY COLUMN ... ALGORITHM=COPY のケースが、オンライン DDL が役に立たないケースです。全行の書き換えが必要ですが、稼働中のテーブルでその間ずっとロックを保持したくはありません。gh-ost は、新しいスキーマを持つテーブルのコピー(「ゴースト」テーブル)を作り、既存の行をバックグラウンドでそこにコピーしながら、トリガーを使う代わりにバイナリログをストリーミングして、コピー中に元のテーブルに行われたすべての書き込みを再生することで、これを処理します。最後に短いアトミックなリネームでカットオーバーします。
gh-ost は、別のテーブルが外部キーで参照しているテーブルの移行を拒否します。前のセクションの order_items テーブルは FOREIGN KEY (order_id) REFERENCES orders(id) を持っており、その制約を残したまま orders に対して gh-ost を実行すると、FATAL found 1 parent-side foreign keys on ... orders. Parent-side foreign keys are not supported. Bailing out ですぐに失敗します。他のテーブルが参照しているテーブルに対して gh-ost を実行する前に、その外部キーを先に削除してください(または --discard-foreign-keys を渡します。これは外部キーを代わりに削除し、再作成はしません)。
これをインストールし、order_items の外部キーを削除した状態で、まさにこの種の変更のために、同じ 3,276,802 行のテーブルに対して実行しました。
--password ではなく -conf ファイルで渡してください。コマンドラインのパスワードは、ホスト上の他のどのユーザーからも ps で見え、シェルの履歴にも残ります。
--execute なしでは、gh-ost はドライランモードで動作します。接続し、binlog をストリーミングし、ゴーストテーブルに使うであろう CREATE TABLE ステートメントを出力しますが、変更は行いません。--execute を付けると、進捗を出力しながら、完全な移行を 13.112 秒で実行しました。
orders テーブルに対する並行する INSERT は 0.007 秒で返り、gh-ost 自身の進捗行が、その書き込みを拾って再生したことを確認しました(Applied: 1)。
SHOW CREATE TABLE orders は customer_id を bigint として示し、行数は正確に一致し(3,276,803、元の行数に移行中に挿入した 1 行を足したもの)、移行の途中で挿入した行は正しい値で存在していました。gh-ost は元のテーブルを削除するのではなく _orders_del にリネームしたので、移行前のテーブルは、カットオーバーを確認した後で比較したり手動で削除したりするためにまだ残っています。
これは、binlog_format=ROW と log_bin=ON がすでに設定されたスタンドアロンのサーバーに対して実行しました。gh-ost は変更をストリーミングするためにこれらを必要とし、ステートメントベースのレプリケーションでは動作しません。また、テーブルに主キーまたは一意キーがあることも必要です。どちらもよくあるデフォルトなので、ここでは追加の設定は不要でしたが、確認していないテーブルに対して gh-ost に頼る前に、両方を確認してください。
よくある質問
ALTER TABLE がテーブルをブロックするかどうかを事前に知るにはどうすればよいですか?
実行する前に、ステートメントにALGORITHM=INPLACE, LOCK=NONE(または LOCK=NONE だけ)を追加します。MySQL がブロックせずに変更できるなら、それは実行されます。できないなら、実行してブロックする代わりに、すぐにエラーを返します。どちらにしても、推測したりソースコードを読んだりせずに確定的な答えが得られます。
列の型変更で ALGORITHM=INSTANT が失敗したのはなぜですか?
INSTANT は、MySQL が変更をメタデータの更新だけで表現できる場合にのみ機能します。列の保存型を変更するということは、既存のすべての行のディスク上の表現が新しい型にとって誤りになるということで、行の読み取りと書き換えが必要になります。これは INSTANT を除外し、特に型変更については INPLACE も除外します。COPY だけがそれを行えます。
gh-ost にはレプリケーションの設定が必要ですか?
いいえ。移行対象のサーバーのバイナリログを読み取ります。これは、binlog_format=ROW のサーバーならレプリカの有無に関係なく生成しているものです。gh-ost はレプリカに対して実行してマスター上でカットオーバーすることもサポートしており、非常に大きい、または非常にホットなテーブルにはそちらのほうが安全なパターンですが、それはデプロイ上の選択であって、そもそも動作させるための要件ではありません。
gh-ost の完了後、古いテーブルはどうなりますか?
デフォルトでは、gh-ost は元のテーブルを削除する代わりに、サフィックス(上で記録した実行では _del)を付けてリネームします。それは、自分で削除するまで、使われないままスキーマに残ります。
トラブルシューティング
関連項目
- MySQL のスキーマ移行 — このガイドが実行する
ALTER TABLEステートメントを生むマイグレーションファイルの整理とバージョン管理 - MySQL のインデックスの仕組み — オンラインの
ADD INDEXで MySQL が構築するインデックスが、その後のクエリパフォーマンスにどう影響するか - MySQL の EXPLAIN 出力の読み方 — 移行の完了後、新しく追加したインデックスが実際に使われていることを確認する