🗻

MySQLのDDLを安全に使うための全て

2025/03/12に公開

これはなに

ども、LT開発部のもりたです。

今回はMySQLのスキーマ変更(DDL)について調べました。
DDLってなんとなく使うことは可能なんですが、データ量が増えていくにつれて障害の要因になったりもしますよね。もりたも障害が起きてDDLを調べたクチなんですが、調べれば調べるほどDDLに関する断片知識が絡まり合い、整理をつけるのが大変でした。今回はその断片を撚り合わせ、綺麗に縫合することで、この1記事だけで大体全てがわかるようにしました。もしDDLでお困りの方がいらっしゃいましたら、この記事(と、そこから辿れる諸記事)を足がかりに基礎と応用を身につけていただければと思います。

また、有識者の方々は是非まさかりを構えながら読んでください。技術的な間違いや現場的にはこうだよという事項がありましたら、コメント欄に記載いただけると幸いです。

構成

構成

まず構成ですが、以下の通り進めます。

  • 前提知識
  • 利用の仕方
    • DDLの流れ方を調べる
    • 対象テーブルを調べる
    • リスクと対策
  • その他

DDLは理解しようとするといくつかの前提知識を求められます。そのためまずは前提知識を整理できる章を用意しました。いくつかの調べごとを通じてDDL実行のリスクと対策を立てていこうというものです。なお、最後にその他として障害事例をご紹介しております。こちらも読むだけで参考になるので、ぜひお読みください。

4つの前提知識

DDLについて知る上で、いくつか事前知識として知っておくと良いものがあります。少し長いですが、サラッと目を通しておくとDDLってどんなものなの? という疑問が全て解決します。

以下の通りです。それぞれ解説します。

  • 前提の前提:DDLの概要
  • 前提①:DDLの種類は3つある
  • 前提②:INPLACE方式のバリエーションは4つ
  • 前提③:DDLは占有ロックをとる
  • 前提④:DDLのとっているロックはメタデータロック

前提の前提:DDLの概要

DDLとはテーブルのスキーマ定義を行うクエリのことです。ALTERから始まるので、ALTERと呼ばれることもあります[1]

UPDATEやINSERT, DELETEなどとは違い実データをレコード単位で触ることはなく、テーブル単位に存在するメタデータを触る点が特徴です。そのため、取得するロックもテーブルレベル以上になり、影響範囲が大きなクエリです。

その影響範囲の大きさゆえに、以前は「DDLを実行する=メンテを入れる」のが一般的でしたが、MySQLの技術的進歩とともにメンテを入れずとも実行することができるようになってきました。

前提①:DDLの実行方式は3つある

ではその技術的な進歩を見てみましょう。DDLの実行方式は3つあります。

COPY方式

5.5以前から存在する実行方式です。
ALTER処理の内実は「スキーマ変更の対象テーブルと全く同じシャドウテーブルを作成し、そこにスキーマ変更を適用する。その後データを移し替える」というものでした。差し替え作業中は共有ロックをとり、また処理自体もとても重たかったため長期間にわたって並行DMLが許可されませんでした。

INPLACE方式

5.6から始まった新しい実行方式です。
COPY方式の課題であった遅さ共有ロックの問題を一部解決しました。DDLの種類にもよりますが、処理が高速になり並行DMLも許可されるようになり、システムを稼働させたままスキーマ変更をすることが現実的になりました。一般にオンラインDDLやオンラインALTERと呼ばれるのはこのためになります。
ただし、INPLACEでは実行できないDDLも存在し、INPLACE方式の中でも並行DMLを許可しないものも存在します。

INSTANT方式

8.0から始まった新しい実行方式です。
実行できるものは限られるが、非常に高速に処理が完了します。

実行方式の歴史と指定方法

DDLの歴史は、COPY方式の持っていた課題を実行速度の高速化並行クエリを許可させるという観点で改善してきた歴史です。

これらの実行方式とロックの厳しさはDDLを流すときに自動的に選択されます。もし自分でこの実行方式を利用したいときは、ALGORYTHM句やLOCK句を指定できます。

ALTER TABLE sample_tbl ADD INDEX(col_1), ALGORITHM=INPLACE, LOCK=NONE;

前提②:INPLACE方式のバリエーションは4つ

前提①にも書いたとおり、INPLACE方式にはさらに4つのバリエーションに分けられます。

  1. メタデータ(カラム名など)の更新のみ
  2. オブジェクトの追加
  3. オブジェクトの削除、入れ替えなど
  4. 既存オブジェクトの更新

この違いによって内部的な処理内容が変わり、速度や取得するロックが変化します。詳しい内容は以下の記事に書いてあるのでぜひご覧ください。[2]

https://zenn.dev/levtech/articles/25462f51c9ffcb

前提③:DDLは占有ロックをとる

MySQL公式ドキュメントによるとDDLは3つのフェーズにわけられます。

  • フェーズ 1: 初期化

    初期化フェーズでは、サーバーは、ストレージエンジンの機能、ステートメントで指定された操作、およびユーザー指定の ALGORITHM オプションと LOCK オプションを考慮して、操作中に許可される同時実行性を決定します。 このフェーズでは、現在のテーブル定義を保護するために、アップグレード可能な共有メタデータロックが取得されます。

  • フェーズ 2: Execution

    このフェーズでは、ステートメントが準備されて実行されます。 メタデータロックが排他的にアップグレードされるかどうかは、初期化フェーズで評価される要因によって異なります。 排他的メタデータロックが必要な場合は、ステートメントの準備中にのみ簡単に取得されます。

  • フェーズ 3: テーブル定義のコミット

    テーブル定義のコミットフェーズでは、メタデータロックが排他的にアップグレードされ、古いテーブル定義が削除されて新しい定義がコミットされます。 付与されると、排他的メタデータロックの期間が短くなります。

参照元:15.12.2 オンライン DDL のパフォーマンスと同時実行性

ここで重要なのは取得するロックです。(記載がありませんが)初期化フェーズとコミットフェーズでは占有ロックを取得します。
以下は3つの実行方式がどんなロックを取るのかを表現した図です。

DDLは実行方式に関わらず必ず占有ロックをとります。その時はSELECTすら流れません。DDLを流して処理が詰まるのはこのせいです。詳細は「リスクと対策」の章にまとめます。

前提④:DDLのとっているロックはメタデータロック

DDLが占有ロックをとるせいで並行クエリが止まってしまうということを上では書きました。

その際に取得するロックはメタデータロックです。実オブジェクトのロックではなく、テーブルのメタデータに対してのロックです。MySQLのデータには実オブジェクトとメタデータがあり、これらを混同しないことが大切です。
なお取得するロックの種類については、以下のスクラップで調べました。

叩いて学ぶ! そのSQLどんなロックを取ってんの!?

また、こちらの記事を読むことでより深くメタデータロックについて知ることができますので、ご参照ください。
https://www.alibabacloud.com/blog/mysql-deep-dive---implementation-and-acquisition-mechanism-of-metadata-locking_599191

利用の流れ

お疲れ様でした! ここからやっと本編です。
この記事の目的は安全にDDLを実行する上で気にしたい情報をほとんど全てまとめることです。DDLはその種類や対象テーブルの事情によっては大きなリスクを抱えることもあります。そのため、「DDLの種類」「対象テーブルの情報」を把握した上で「リスクへの対策」をとっていくことが大切です。
なおALTERの書き方等は紹介しませんので、公式ページや各種技術記事等を参考にしていただければ幸いです。

それでは参りましょう〜!

DDLの種類を調べる

これまで解説してきた通り、DDLにはさまざまな実行パターンがあり、ロックの取り方や実行時間の長い短いが変化します。それによっては障害につながりやすいものもありますので、まずは流したいDDLがどんな種類のDDLなのかを把握する必要があります。

DDLのパターンはざっくり6種類[3]に分けられると考えています。

  1. インスタント
  2. インプレース - メタデータ更新のみ
  3. インプレース - オブジェクト追加のみ
  4. インプレース - オブジェクト削除や並び替え、一部テーブル操作
  5. インプレース - 既存オブジェクトの更新あり
  6. コピー
どうしてこの6パターンなの?

どうしてこの6パターンなの?

「4つの前提知識」に記載した内容ですが、INSTANT方式1種類、INPLACE方式4種類、COPY方式1種類で6パターンになっています。
また、後述するDDLの特徴を考慮するとこのような図が描けます。

ここら辺の背景については別記事を書いていますので、こちらをご参照ください。

https://zenn.dev/levtech/articles/25462f51c9ffcb

この6種類のうちどれに収まるのかは「公式ドキュメントの一覧で調べる」「実際に流してみて確認する」で調べることができます。

公式ドキュメントの一覧で調べる

DDLがどんな実行方式・特徴を持っているのかは公式ドキュメントに一覧があります。

一例を出すとこんなふうに整理されています。

操作 インスタント インプレース テーブルの再構築 同時 DML の許可 メタデータの変更のみ
セカンダリインデックスの作成または追加 いいえ はい いいえ はい いいえ

そして、6パターンとの対照表はこの通りです。

パターン インスタント インプレース テーブルの再構築 同時DMLの許可 メタデータの変更のみ
①インスタント はい はい いいえ はい はい
②インプレース - メタデータ更新のみ いいえ はい いいえ はい はい
③インプレース - オブジェクト追加のみ いいえ はい いいえ はい いいえ
④インプレース - オブジェクト削除や並び替え、一部テーブル操作 いいえ はい はい はい いいえ
⑤インプレース - 既存オブジェクトの更新あり いいえ はい はい いいえ いいえ
⑥コピー いいえ いいえ はい いいえ いいえ
⑦その他(一例) いいえ はい いいえ いいえ いいえ

一例に示した「セカンダリインデックスの作成または追加」は「③インプレース - オブジェクト追加のみ」に該当します。
自分の流したいDDLがどのパターンに当てはまるか確認してみましょう。

実際に流してみて確認する

もうひとつの確認方法は、実際に流してみることです。
ここではDDLをテスト環境などで流して2点の確認をします。

  1. 指定できるALGORYTHM句, LOCK句はどれか
  2. DDL適用後に影響のでたレコードがあるか

まずひとつ目ですが、MySQLのDDLは以下のように実行方式とロックを指定できます。このとき、利用不可能な指定をするとエラーを返します。

ALTER TABLE sample_tbl ADD INDEX(col_1), ALGORITHM=INPLACE, LOCK=NONE;
-- ALGORITHM=INSTANT → INPLACE, LOCK=NONE → SHARE のようにより厳しいものから試していく

これによって、流したいDDLの実行方式とロックがわかります。

そしてふたつ目。
詳しくは公式ドキュメントを見ていただければと思うのですが、DDLの実行後に以下のように影響のでたレコードがある場合、それはテーブルの再構築が行われています。

Query OK, 1671168 rows affected (1 min 35.54 sec)

以上の確認により、実行方式、ロックのレベル、テーブルが再構築するかどうかが分かります。
ただそれが分かった時にどうDDLの種類の確認に活かせるんだ...という点が分かりにくいため、以下に確認のためのフローチャートを書きました。

流す対象のテーブルを調べる

流すDDLに関する情報が得られたら、次はDDLを適用する対象のテーブルについても調べましょう。レコード数が200行しかないテーブルと、200万行あるテーブルでは同じDDLを流してもリスクになる度合いは違いますよね? そういったことを調べます。
ここで調べたい情報は2種類に分けられます。テーブルの情報と流れるクエリの情報です。

テーブルの情報を調べる

テーブルの情報で調べたい項目はだいたい以下です。[4]

  • テーブルの容量
  • テーブルのレコード数
  • 親テーブルの有無

容量、レコード数に関する情報は以下のクエリで出せます。

SELECT  
    table_name, engine, table_rows AS tbl_rows,
    avg_row_length AS rlen,  
    floor((data_length+index_length)/1024/1024) AS allmb,  #総容量
    floor((data_length)/1024/1024) AS dmb,  #データ容量
    floor((index_length)/1024/1024) AS imb   #インデックス容量
FROM 
    information_schema.tables  
WHERE
    table_schema=database()  
ORDER BY
    (data_length+index_length) DESC; 

参照: MySQLでDBとテーブルのサイズを確認するSQL - Qiita

information_schema.tablesは概算値であるため、大きな更新が行われた後だと実態とずれている可能性があります。その場合はINFORMATION_SCHEMA.INNODB_TABLESPACES[5]に実測値が入っているので、対象のテーブルをSELECT NAME,FILE_SIZE,ALLOCATED_SIZE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME LIKE '%テーブル名%';[6]等でクエリして実測値と概算値の差分があるか確かめてみましょう。

流れるクエリを調べる

続いて対象のテーブルに流れるクエリの種類を調べます。
クエリの種類に関しては実施する組織によって色々と事情の異なる部分なので、調べる観点と代表的な調べ方を紹介するにとどめます。

観点

観点は4点あります。

  • 種類:参照/更新のどちらか
  • 頻度:実行頻度はどの程度か
  • 期間:ロングトランザクションを張っているものはないか
  • 性質:どの程度重要な機能に利用されているか

方法

代表的な調べ方は以下の通りです。

  • MySQLの一般クエリログ/スロークエリログで調べる
  • アプリケーションログで調べる
  • NewRelicなどのo11yツールで調べる

自組織で利用できる方法にどんなものがあるのか検討してみてください。

リスクと対策

ここまででDDLの種類と実行対象となるテーブルの特性を把握しました。
以下ではそれらの情報をもとにリスクを洗い出し、取れる対策まで解説します。

基本 - どんなDDLでもできる対策

細かなリスクを見ていく前に、どんなDDLでもできる対策を以下に3つご紹介します。

実行時間の制限

ひとつ目は実行時間の制限です。

MySQLでは特に設定していない場合、経過時間起因でクエリが自動的にKillされることはありません。そのため、長いクエリを流すと流れ終わるまでずっと対象テーブルに対してロックを取り続け、障害の原因になることがあります。
これに対してはmax_execution_timelock_wait_timeoutを設定することで、クエリの実行時間に制限をかけることが可能です。前者はSELECTに対する実行時間の制限となるためDDLに先行するロングトランザクションへの対策です[7]。後者はメタデータロックを取得するために待機する時間を制限でき、DDL自体が長く待ってしまうことを防げます[8]
これらは広範囲への影響を防ぐことはできるものの、DDL自体の失敗する蓋然性は増えてしまいます。

ALGORYTHM, LOCK句をつける

ふたつ目はDDLを流す際にALGORYTHM, LOCK句をつけることです。

このオプションをつけることで何かより良い実行方式が選べるわけではないですが、少なくとも意図しない実行方式やロックでDDLが流してしまうといったケアレスミスを防ぐことができます。

DDL実行の監視

三つ目はDDL実行の監視です。DDLの実行が正常に行われているかどうかをリアルタイムでチェックします。

これによって、予測していたよりも長い間DDLが実行されているケースをキャッチし、必要に応じてプロセスをKillするなどの対応ができます。
具体的な監視方法の一例は以下があります。

https://dev.mysql.com/doc/refman/8.0/ja/monitor-alter-table-performance-schema.html

発展 - DDLパターンとテーブル情報に応じた対策

ここではDDLのパターンとテーブルの情報に応じた対策をご紹介します。

まず、DDLパターンに応じて推測される代表的なリスクを下記にまとめました。[9]

パターン 占有ロックでクエリが詰まる デッドロック 並行DMLによる一時ログファイル容量超過 再構築時のソートファイル・中間テーブルファイル超過 更新クエリの長期ブロック 処理の速度
①インスタント × × × とても高速
②インプレース - メタデータ更新のみ × × 高速
③インプレース - オブジェクト追加のみ × × まあまあ
④インプレース - オブジェクト削除や並び替え、一部テーブル操作 × 低速
⑤インプレース - 既存オブジェクトの更新あり ×[10] 低速
⑥コピー ×[11] とても低速

○はリスクがある、×はリスクがないという意味です。システムの特性やテーブルの情報によってリスクの大きさは増減します。
上記マトリクスに挙げた代表的な5つのリスクに関して「リスクの概要」「リスクを増加させるテーブル要因」「対策」の3点で以下にまとめます。

リスク① - 占有ロックでクエリが詰まる

リスクの概要

DDLが実行の開始/終了時にテーブルレベルで占有ロックをとるために発生します。DDLは先行するロックが全て解除されるまで待機し、そして並行クエリはDDLがロックを放棄するまで待機します。図にするとこの通りです。


一番悩まされる仕様です

先行クエリがロングトランザクションを張っていた場合、DDLを含む後続のクエリが全て待機状態になります。

リスクを悪化させるテーブル情報

  • ロングトランザクションやスロークエリが頻発するテーブル
  • 参照・更新頻度が高く、クエリが詰まった場合の影響が大きい
  • 重要な機能を担っている

対策

  • max_execution_timelock_wait_timeoutの適切な設定
  • DDL実行前にロングトランザクションがないかチェックする
  • DDL実行を監視し、詰まった時に先行クエリかDDL自体をKillする
  • そもそも長いクエリが流れないように日頃から改善をしておく

リスク② - デッドロック

リスクの概要

こちらもDDLが取得する占有ロックに起因します。
詳しくは以下の記事で検証されていますが、並行するトランザクション内で占有ロックを挟み込むとデッドロックが発生します。デッドロックが発生した場合、DDLではない並行するトランザクションの処理がロールバックします。[12]

MySQLのオンラインDDLでDeadlockエラーになるケース - あおうさ@日記

リスクを悪化させるテーブル情報

  • トランザクションを張る処理が頻繁に流れている
  • ロールバックされて欲しくない重要な機能を担っている

対策

  • リスク許容
  • デッドロックが発生した際に影響を調査しやすい仕組み作り
    • 設定でinnodb_print_all_deadlocksを有効にする
  • メンテナンスやその他の方法で並行クエリを許容しない

リスク③ - 並行DMLによる一時ログファイル容量超過

リスクの概要

DDLと並行してDMLが実行される場合、MySQLではその実行内容が一時ログファイルに溜まっていきます。この一時ログファイルの容量が決まっており、長い間並行DMLが流れる処理では考慮が必要です。

参考:15.12.3 オンライン DDL 領域の要件 - MySQL公式

リスクを悪化させるテーブル情報

  • 頻回更新のテーブルである
  • 一時ログファイルの最大容量が低く設定されている
    • innodb_online_alter_log_max_sizeで設定される

対策

  • 一時ログファイルに使える容量を増やす
  • メトリクスを監視しておく
  • 必要であればメンテナンスやリリースタイミングの調整などで並行DMLが流れない状況を作る

リスク④ - 再構築時のソートファイル・中間テーブルファイル超過

リスクの概要

こちらも領域に関するものです。DDLによるテーブルの再構築には大きなコストがかかります。また再構築時にはテーブルのコピーを取るのですが、そのコピーを置いておくためのストレージ容量が求められます。

参考:15.12.3 オンライン DDL 領域の要件 - MySQL公式
参考事例:大きなテーブルのスキーマ変更を RDS Blue/Greenデプロイでダウンタイムなく実行 & マイグレーションとも辻褄を合わせる - Zenn

リスクを悪化させるテーブル情報

  • テーブル容量が大きい
  • ストレージ容量が足りていない

対策

  • ディスク容量の残りを確認し、必要に応じて増強する
    • 確認方法は上記参考事例記事に記載があります
  • テーブル構造などの見直し

リスク⑤ - 更新クエリの長期ブロック

リスクの概要

並行DMLを許可しないパターンのDDLでは並行する更新クエリを長期にわたってブロックします。また、並行DMLが許可されないパターンは大体クエリが重たいため、更新クエリが長期にわたってブロックされるというリスクもあります。

リスクを悪化させるテーブル情報

  • テーブル容量が大きく、DDL実行完了までに時間がかかる
  • 重要な更新クエリが流れている

対策

おわりに

この記事は以上で終わりです。
DDLの仕組みや気をつけるポイントについてはだいたい網羅できたかと思いますが、実運用の中で全てをやろうとすると息切れしてしまうでしょう。どういった形で低コストに/安全にDDLを適用していくかはまた別問題なのですが、ひとまずこの資料がそのガードレール設置の一助になることを願っています。

補遺・参考文献

補遺

ここでは構成の都合上、本編に入れられなかったものを雑多に記載します。

実際どれくらい気をつければいいか

実際どれくらい気をつければいいか

ほんとに対象のシステムによるとしか言えないんですが、DDLのパターンで①〜②、もしくは③くらいまではあんまり何も気にせず流せるんじゃないかと思っています。そして、DDLの大半はそこに収まっているはずです。
問題は④以降で、テーブルの再構築を含むDDLはかなりコストが高くなるため、メンテナンスが必要になったりすることもあるかなと考えています。
ただ繰り返しますが、システムによります。

観測したDDL起因の障害

観測した障害

長々と記事を書きましたが、発生しうる不具合については事例を読むのが一番理解が早いです。以下ではDDL実行にまつわる様々な事例をご紹介します。

気軽に流したDDLが並行DMLを許可しておらず死にかけた話

https://kaminashi-developer.hatenablog.jp/entry/2023/10/23/mysql-online-ddl

参照が詰まったと記載あるので、並行DMLの問題というより先行するロングトランザクションがあったのではないかと推測

ALTER一撃でサービス停止

https://qiita.com/shuCrane/items/94d3038b5feaf614778f

タイトルが良すぎる。
親テーブルがありそう。外部キーの設定によってはSHAREでやるので、DMLはつまりそう。
けどこちらも読み込みが失敗とあるので、おそらくはDDL実行前後のロングトランザクション

デッドロック

https://bluerabbit.hatenablog.com/entry/2022/12/07/123143

障害ではないですが、DDLでデッドロックを起こしてみた的な記事。
おそらくメタデータロックに特徴的な挙動が含まれているはず。以下の記事でメタデータロックのデッドロック検出まで解説されているのでこちらでわかりそう。
https://www.alibabacloud.com/blog/mysql-deep-dive---implementation-and-acquisition-mechanism-of-metadata-locking_599191

メモリとストレージの激減

https://zenn.dev/linkedge/articles/7a2eddb0f8b0b3

これもめちゃくちゃいい記事。

外部キーの追加でも処理が詰まった事例

https://simon-ninon.medium.com/dont-break-production-learn-about-mysql-locks-297671ec8e73
海外の記事。面白い事例でした。

その他の小ネタ, 扱いきれなかったもの

小ネタ

扱いきれなかったもの

  • パーティションのDDL
  • replica
    • 「オンライン DDL 操作を長時間実行すると、レプリケーションラグが発生する可能性があります。 オンライン DDL 操作は、レプリカで実行する前にソースで実行を終了する必要があります。 また、ソースで同時に処理された DML は、レプリカでの DDL 操作が完了した後にのみレプリカで処理されます。」
    • 15.12.6 オンライン DDL の制限事項

書籍

Web上の情報

脚注
  1. CREATE TABLEとかもあるので厳密には違いますが、そう呼ばれることが多いです ↩︎

  2. 3は書いてないですが、DDLを使っていると存在しそうだなとわかる。詳しくは「利用の流れ」にて後述する一覧を参照してください ↩︎

  3. 例外はありますがだいたい6種類。なおこれは公式ドキュメントに書かれていたり何かの書籍に記載があるわけではないので、違うだろと思ったらコメントいただけると嬉しいです ↩︎

  4. 他にも気にしているポイントがあったら教えてください ↩︎

  5. 8.0以降。5.7ではinformation_schema.innodb_sys_tablespaces ↩︎

  6. 26.51.25 INFORMATION_SCHEMA INNODB_TABLESPACES テーブル - MySQL公式 ↩︎

  7. max_execution_timeを設定してMySQLクエリの実行時間を制限する - gihyo ↩︎

  8. 参考: MySQLのさまざまなタイムアウトオプションについて - gihyo ↩︎

  9. その他のリスクとしてDBサーバのパフォーマンス負荷なども挙げられます。 ↩︎

  10. 並行DMLを許可しないため ↩︎

  11. 並行DMLを許可しないため ↩︎

  12. オブジェクトのロックだとこういうことは起きないはずなんですが、おそらくメタデータロックであるが故に起きているっぽい。詳しくは後日まとめます ↩︎

レバテック開発部

Discussion