MySQLでカラムを追加する方法:ALTER TABLE ADD COLUMNの基本・実例

既存テーブルにカラムを1本足すだけでしょ?」——そう思って本番DBで実行し、長時間ロックや想定外のエラーに慌てた経験はありませんか。
本記事では、ALTER TABLE ... ADD COLUMN の基本構文から、FIRST / AFTER による位置指定、NOT NULL・デフォルト値の扱い、複数カラムの同時追加、オンラインDDLパラメータ(ALGORITHM / LOCK)の使い方、そして大規模テーブルでも安全に進めるための実務的な手順やチェックリストまで、網羅的に解説します。


なぜ「カラム追加」は怖いのか(ロック・時間・レプリケーションへの影響)

テーブル定義の変更(DDL)は、サイズの大きいテーブルでは長時間のテーブルロックや再構築を伴い、アプリケーションに影響を与える可能性があります。特に以下を意識しましょう。

  • 追加するカラムに NOT NULLDEFAULT を伴うと、全行の再書き込みが発生するケースがある
  • MySQLのバージョンやストレージエンジン(多くはInnoDB)、指定する ALGORITHM により、オンラインで変更できるかどうかが変わる
  • レプリケーション環境では、スレーブでの適用時間差が発生し、遅延を引き起こす可能性がある

基本構文:ALTER TABLE … ADD COLUMN

最も基本的な構文は次のとおりです。

ALTER TABLE テーブル名
ADD COLUMN 新カラム名 データ型 [NOT NULL | NULL] [DEFAULT デフォルト値];

例:

ALTER TABLE users
ADD COLUMN age INT NULL;

NULL / NOT NULL の指定、DEFAULT の有無で挙動やコストが変わる点を覚えておきましょう。

カラム位置を指定する:FIRST / AFTER

MySQLでは、カラムをテーブルの先頭や任意のカラムの後ろに差し込めます。

-- 先頭に追加
ALTER TABLE users
ADD COLUMN created_by VARCHAR(50) NULL FIRST;

-- 任意のカラムの後ろに追加
ALTER TABLE users
ADD COLUMN updated_by VARCHAR(50) NULL AFTER created_by;

注意:アプリ側が「SELECT * の列順」に依存していると破壊的変更になります。アプリで列名指定を徹底しましょう。

NOT NULL と DEFAULT の安全な付け方

既存データに値がない状態で NOT NULL を付けるとエラーになります。よく用いられる手順は以下。

  1. NULL許可でカラム追加(DEFAULTは付けない)
    ALTER TABLE users ADD COLUMN status TINYINT NULL;
  2. 既存行を更新
    UPDATE users SET status = 0 WHERE status IS NULL;
  3. NOT NULL + DEFAULTを付ける
    ALTER TABLE users MODIFY COLUMN status TINYINT NOT NULL DEFAULT 0;

この三段階に分けることで、テーブル再構築のコストやロック時間を抑えられるケースがあります(バージョン・データ量次第)。

複数カラムを一度に追加する

DDLは1回にまとめた方がテーブル再構築の回数を減らせて効率的です。

ALTER TABLE orders
ADD COLUMN shipped_at DATETIME NULL,
ADD COLUMN shipped_by VARCHAR(100) NULL;

ただし、1回のDDLが長引くと影響範囲も広がるため、サイズ・時間・リスクのバランスは要検討です。

オンラインDDLを意識する:ALGORITHM / LOCK 句

MySQL 5.6以降(InnoDB)では、オンラインDDLを部分的にサポートしています。MySQL 8.0系ではさらに改善されています。以下のように指定できます。

ALTER TABLE users
ALGORITHM=INPLACE,
LOCK=NONE
ADD COLUMN last_logged_in DATETIME NULL;
  • ALGORITHM=INPLACE:テーブルコピーを避け、より低コストで変更できる可能性
  • LOCK=NONE:読み書き可能な状態を維持しながらDDLを実行(常に可能とは限らない)

ポイント:指定したパラメータが非対応の場合、エラーで止まるので「静かにテーブルコピーに落ちる」より安全です。

大規模テーブルで「安全に」追加する手順

  1. ステージングでのテスト:実データ量に近い環境で事前計測
  2. メンテナンス時間帯の確保(必要なら)
  3. NULL許可 → 値の埋め込み → NOT NULL化 の三段階戦略
  4. pt-online-schema-change(Percona Toolkit)などのツール利用の検討
    • 既存テーブルをコピーしながらトリガーで差分同期、最後に入れ替えることでダウンタイムを最小化
  5. レプリケーション遅延の監視
  6. ロールバック手順(旧スキーマに戻すSQL)を用意
  7. アプリケーションのデプロイ順序を設計(先にコードで新旧スキーマ両対応 → DDL実行 → 古いカラム/コードを削除)

生成列(Generated Column)を追加する

MySQLでは計算結果を格納/非格納する生成列が使えます。

-- VIRTUAL(計算のみ、ストレージを消費しない)
ALTER TABLE sales
ADD COLUMN total_price DECIMAL(10,2)
AS (unit_price * quantity) VIRTUAL;

-- STORED(値を保存、インデックスも貼れる)
ALTER TABLE sales
ADD COLUMN total_price DECIMAL(10,2)
AS (unit_price * quantity) STORED;

STORED はテーブル再構築が必要になる場合があるため、オンラインDDL可否を確認しましょう。

JSON列やENUM列を追加する時の注意

  • JSON:MySQL 5.7以降で正式対応。デフォルト値を {} にしたい場合は DEFAULT (JSON_OBJECT()) と書けないバージョンもあるため、まずNULL許可で追加し、アプリ側で埋める運用も選択肢です。
  • ENUM:後から値を追加するのが面倒(DDLが必要)なので、将来拡張を見越し TINYINT + マスタテーブル で管理する方が保守性が高い場合が多いです。

よくあるエラーと対処

  • ERROR 1060 (42S21): Duplicate column name
    • すでに同名カラムが存在。DESC テーブル名; などで確認。
  • ERROR 1364 (HY000): Field ‘xxx’ doesn’t have a default value
    • NOT NULL かつ DEFAULT なしで既存行に値がない。三段階手順で回避。
  • ALGORITHM/LOCKが非対応
    • 明示指定している場合はエラーで分かる。サーババージョン・DDL種類を確認し、対応可能なパラメータに変更。

変更を戻したい(ロールバック)

DDLは基本的にトランザクションで簡単にロールバックできません。戻したくなった際に即座に実行できるよう、逆方向のDDL を用意しておきましょう。

sqlコピーする編集するALTER TABLE users
  DROP COLUMN status;

ただし、アプリ側が既に新カラムを参照していると逆に障害になります。デプロイ順序とフェーズドリリース(新旧スキーマ両対応期間を設ける)が鍵です。

まとめ(チェックリスト付き)

  • まずロックや再構築の可能性を理解し、本番前にステージングで計測する
  • NOT NULL + DEFAULT は重いため、NULL許可 → 値埋め → NOT NULL化 の三段階戦略を検討
  • ALGORITHM=INPLACE / LOCK=NONE を明示し、非対応ならエラーで止める
  • 複数カラムはまとめるが、DDLが長時間化しすぎないように注意
  • pt-online-schema-change 等のオンラインスキーマ変更ツールの活用も視野に
  • レプリケーション遅延アプリのデプロイ順序を必ず設計
  • ロールバック手順(逆DDL)を用意しておく
タイトルとURLをコピーしました