既存テーブルにカラムを1本足すだけでしょ?」——そう思って本番DBで実行し、長時間ロックや想定外のエラーに慌てた経験はありませんか。
本記事では、ALTER TABLE ... ADD COLUMN
の基本構文から、FIRST
/ AFTER
による位置指定、NOT NULL
・デフォルト値の扱い、複数カラムの同時追加、オンラインDDLパラメータ(ALGORITHM
/ LOCK
)の使い方、そして大規模テーブルでも安全に進めるための実務的な手順やチェックリストまで、網羅的に解説します。
なぜ「カラム追加」は怖いのか(ロック・時間・レプリケーションへの影響)
テーブル定義の変更(DDL)は、サイズの大きいテーブルでは長時間のテーブルロックや再構築を伴い、アプリケーションに影響を与える可能性があります。特に以下を意識しましょう。
- 追加するカラムに
NOT NULL
+DEFAULT
を伴うと、全行の再書き込みが発生するケースがある - 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
を付けるとエラーになります。よく用いられる手順は以下。
- NULL許可でカラム追加(DEFAULTは付けない)
ALTER TABLE users ADD COLUMN status TINYINT NULL;
- 既存行を更新
UPDATE users SET status = 0 WHERE status IS NULL;
- 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を実行(常に可能とは限らない)
ポイント:指定したパラメータが非対応の場合、エラーで止まるので「静かにテーブルコピーに落ちる」より安全です。
大規模テーブルで「安全に」追加する手順
- ステージングでのテスト:実データ量に近い環境で事前計測
- メンテナンス時間帯の確保(必要なら)
- NULL許可 → 値の埋め込み → NOT NULL化 の三段階戦略
pt-online-schema-change
(Percona Toolkit)などのツール利用の検討- 既存テーブルをコピーしながらトリガーで差分同期、最後に入れ替えることでダウンタイムを最小化
- レプリケーション遅延の監視
- ロールバック手順(旧スキーマに戻すSQL)を用意
- アプリケーションのデプロイ順序を設計(先にコードで新旧スキーマ両対応 → 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)を用意しておく