◀ 4.

MySQL/MariaDBのカラム追加・削除|既存値・互換性・ロックの確認

▶

追加と削除は、戻せる範囲も含めて別に設計する

列の追加・削除と値の復元や利用側互換を分ける図

カラム追加・削除ではSQLの成功だけでなく、既存行の値、NULL/既定値、制約・索引、アプリやviewの互換性を確認します。追加は必ず無害、削除はSQLエラーが消えれば安全、とは言えません。ここはMySQL/MariaDB配下の記事で、他DBの比較コードは後半の補足として区分しています。

1. 接続先を読取り、新しい隔離検体を用意する

SELECT VERSION() AS server_version, DATABASE() AS selected_database,
       @@SESSION.sql_mode AS session_sql_mode,
       @@SESSION.foreign_key_checks AS session_fk_checks;

以降は使い捨て環境だけの例です。cwiki_w25という新しい空DBを用意し、itemsという同名表が存在しないことを確認します。本番や既存DBの名前へ置き換えて試さないでください。DB自体の作成も管理権限/接続先を確認する別操作です。

-- New empty cwiki_w25 fixture DB only; do not reuse an existing items table.
CREATE TABLE cwiki_w25.items (
    id INT NOT NULL PRIMARY KEY, label VARCHAR(30) NOT NULL
) ENGINE=InnoDB;
INSERT INTO cwiki_w25.items VALUES (1,'preserved'),(2,'also preserved');
SHOW CREATE TABLE cwiki_w25.items;
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'cwiki_w25' AND TABLE_NAME = 'items'
ORDER BY ORDINAL_POSITION;

最初のitemsはidとlabelの2列です。実表では列属性に加えて、索引/外部キー/生成式、view/routine/trigger、各writerと外部クライアントの参照も調べます。SHOW CREATEや監視のSQL全文には内部情報が含まれ得るため、そのまま公開しません。

2. NULL許可の列を追加し、既存行を読む

-- Fresh fixture only. This nullable column has no business backfill policy.
ALTER TABLE cwiki_w25.items ADD COLUMN review_note VARCHAR(30) NULL;
SELECT id, label, review_note FROM cwiki_w25.items ORDER BY id;

検体の既存2行のid/labelはそのままで、新しいreview_noteはNULLでした。未知/未入力/該当なし等の意味を検討せず、一律に0やactiveを入れる設計にしません。NOT NULL/DEFAULTの要否はデータの意味と、追加後のアプリのINSERTを含めて決めます。

別の整数列検体では、既存行がある表にINT NOT NULLをDEFAULTなしで追加でき、既存行には0が入りました。しかし、その列を省略した後続INSERTはstrictモードで1364になりました。「DEFAULTなしなら追加は必ず失敗」も「追加できたから旧writerも問題ない」も誤りです。この0補完を業務の推奨値として使う意味ではありません。

3. 削除は値を失う操作。ROLLBACKや再追加で戻らない

次は破壊的なSQLです。この検体で追加したreview_noteだけを対象にしています。実際の業務列へ実行せず、保持・復元方法と利用側の切替を別に設計してください。

-- Destructive example: remove only the newly added disposable fixture column.
-- Never substitute an existing business column or assume ROLLBACK restores it.
ALTER TABLE cwiki_w25.items DROP COLUMN review_note;

検体のreview_noteへ一度値を入れてから削除した検査では、後のROLLBACKでも列は戻らず、同名列を追加し直しても元の値はNULLになりました。id/labelの2行が残ることと、削除列の値を復元できることは別です。旧名を読む検体viewも1356になりました。

DROP COLUMN IF EXISTSは製品/版で構文が異なります。MySQL8.4.13では1064、MariaDB11.8.9で存在しない検体列を指定すると1091の警告でした。IF EXISTSが使えても、存在する列の削除・データ損失・依存への影響を防ぐ機能ではありません。

4. 進捗とロック待ちを、実在する情報で確認する

-- Catalog discovery, not a working progress monitor or a server change.
SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'information_schema'
  AND LOWER(TABLE_NAME) = 'innodb_alter_table_progress';
SELECT COLUMN_NAME FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'information_schema'
  AND TABLE_NAME = 'PROCESSLIST' AND COLUMN_NAME = 'PROGRESS';

これはcatalogを読むだけで、進捗監視を有効化するSQLではありません。検体ではinnodb_alter_table_progressという表は両製品とも見つからず、PROCESSLISTのPROGRESS列はMariaDBだけにありました。MySQLの状態/待ち、Performance Schemaの計測対象と有効条件、MariaDBの全体/段階別進捗を区別します。今回の検体はPerformance Schemaを無効にしており、実際の進捗イベント収集を検証したものではありません。

別接続が読取transactionを保持した検査ではINSTANTのADDもmetadata lockを待って1205になりました。LOCK=NONEも待ちをなくす保証ではありません。行数だけで所要時間を決めず、何の待ちかを調べ、KILLやOPTIMIZE、外部ツールへの切替を自動対処にしません。中止にも取消処理の負荷/時間があり、別の実行計画が必要です。

5. ローリング変更は日数だけで削除を決めない

新しいコードだけでなく、旧worker、queue、バッチ、監視、BI、外部連携が旧列を読書きしていないことを確認します。SELECT *やINSERTの列省略/位置依存もあり、NULL列を追加しただけで全旧アプリの互換性を保証できません。

backfillと並行writerの同期、失敗/再試行時の一致、新列を読む前の検証、切戻しの保護範囲を先に決めます。数日〜1週間の経過やアプリ1本の更新だけを旧列削除の条件にしません。外部ツールの導入/実行、既存表の物理再編成、Laravelのmigrateは今回行っていません。

テーブル関連の親から子ページを選択できます。型/属性は定義変更、名前だけならカラム名変更、関係する制約は外部キーへ進みます。元の色付きADD/DROPも保守資料として残しています。

旧例の注意:変更/削除の比較用です。順番だけで互換性を保証せず、DDLとデータ/アプリ変更を分けます。

この記事の要点
  • カラム追加: ALTER TABLE t ADD col TYPE [DEFAULT ...] [NOT NULL]
  • カラム削除: ALTER TABLE t DROP COLUMN col
  • MySQL は AFTER other_col / FIRST でカラム位置指定可、PostgreSQL は末尾追加のみ
  • ロック/書換え/待ちは製品/版/操作/engine等で異なる。Online DDLや外部ツールも無停止の保証ではない
  • ローリング変更は各writer/読取・同期・照合・削除条件まで設計する。順番だけで互換性を保証しない

カラム追加 (ADD COLUMN)

旧例の注意:同名email追加などの別例を連続実行しません。NOT NULLならDEFAULT必須というコメントは全条件で正しくなく、既存値と後続INSERTを確認します。CHECK/FKの版・型/符号も照合します。

-- 基本: 末尾にカラム追加
ALTER TABLE users ADD COLUMN email VARCHAR(255);

-- NOT NULL + DEFAULT 必須 (既存行のため)
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';

-- NULL 可なら DEFAULT 省略可
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL;

-- 複数カラムを一度に
ALTER TABLE users
    ADD COLUMN first_name VARCHAR(100),
    ADD COLUMN last_name  VARCHAR(100);

-- 制約付き
ALTER TABLE users ADD COLUMN age INT CHECK (age >= 0);
ALTER TABLE users ADD COLUMN email VARCHAR(255) UNIQUE;
ALTER TABLE orders ADD COLUMN user_id BIGINT,
    ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);

カラム位置の指定 (MySQL / MariaDB)

MySQL / MariaDB は AFTER / FIRST でカラム位置を指定できます。他製品へ同じFIRST/AFTER構文を流用せず、対象製品/版の構文を確認します:

旧例の注意:位置変更だけのために実表のDROP/再作成を行いません。MySQL/MariaDBと他製品の直接構文を区別し、SELECT等では列を明示する設計も検討します。

-- MySQL: 既存カラムの後ろに
ALTER TABLE users ADD COLUMN nickname VARCHAR(50) AFTER name;

-- MySQL: 先頭に
ALTER TABLE users ADD COLUMN uuid CHAR(36) FIRST;

-- PostgreSQL: 位置指定不可 (常に末尾)
ALTER TABLE users ADD COLUMN nickname VARCHAR(50);  -- 末尾のみ

-- PostgreSQL で順序を変えたい場合は: 新テーブル作成 → INSERT SELECT
-- → 旧テーブル DROP → 新テーブル RENAME
-- (実用上はカラム順序は気にせずビュー側で並べる)

カラム削除 (DROP COLUMN)

旧例の注意:実行しない旧削除例です。CASCADEは他の依存も削除し得ます。DROP COLUMN IF EXISTSはMySQL8.4では非対応で、対応製品でも誤削除の保護ではありません。

-- 1 カラム削除
ALTER TABLE users DROP COLUMN phone;

-- 複数削除
ALTER TABLE users
    DROP COLUMN phone,
    DROP COLUMN fax;

-- PostgreSQL は CASCADE で依存ビュー等も削除
ALTER TABLE users DROP COLUMN phone CASCADE;

-- IF EXISTS で安全に (PG/MySQL 8 で対応)
ALTER TABLE users DROP COLUMN IF EXISTS phone;

カラム型・名前変更

旧例の注意:MODIFY/CHANGEは型以外の残す属性も再指定します。他製品の構文や複数の別例を、同じusersへ続けて実行しません。

-- MySQL: MODIFY (型のみ変更)
ALTER TABLE users MODIFY COLUMN name VARCHAR(200) NOT NULL;

-- MySQL: CHANGE (名前+型変更)
ALTER TABLE users CHANGE COLUMN name full_name VARCHAR(200) NOT NULL;

-- PostgreSQL: 個別構文
ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(200);
ALTER TABLE users ALTER COLUMN name SET NOT NULL;
ALTER TABLE users ALTER COLUMN name DROP NOT NULL;
ALTER TABLE users ALTER COLUMN name SET DEFAULT 'unknown';
ALTER TABLE users RENAME COLUMN name TO full_name;

-- SQL Server
ALTER TABLE users ALTER COLUMN name VARCHAR(200) NOT NULL;
EXEC sp_rename 'users.name', 'full_name', 'COLUMN';

巨大テーブルでの注意

ALTERの所要時間や並行操作は、行数だけでなく操作/engine/版/索引/データ/負荷/metadata lock等から確認します。表の行数だけで数時間〜数日の停止と決めません。

旧例の注意:行数/版名だけから全コピー・即時・ロックなしとは決めません。操作別の対応条件と実際の待ちを調べます。

操作MySQL 5.7 以前対応版のInnoDBで確認すること
NULL 可カラム追加旧版も操作/engineによりINPLACE等。全てCOPYとは限らないINSTANT等の対応条件とMDL待ちを確認
NOT NULL + DEFAULT カラム追加操作/engine/版の対応と既存値を確認対応条件・既定値の意味・後続INSERTを確認
カラム削除操作/engine/版の対応と値の損失を確認INSTANT/INPLACE等の対応条件。metadata lockなしではない
カラム型変更実際の型/値変換と負荷を確認型変更と文字長拡張等を区別して対応表を確認

MySQL 8 Online DDL

旧例の注意:一瞬/行ロック無しの旧コメントを無停止の保証にしません。INSTANT/INPLACE/LOCK指定が未対応なら失敗する場合もあります。

-- INSTANT アルゴリズム指定 (一瞬で終わる)
ALTER TABLE users
    ADD COLUMN nickname VARCHAR(100),
    ALGORITHM=INSTANT;

-- INPLACE + LOCK=NONE で行ロック無し
ALTER TABLE users
    ADD INDEX idx_email (email),
    ALGORITHM=INPLACE,
    LOCK=NONE;

-- 失敗時のフォールバック制御
ALTER TABLE users
    DROP COLUMN deprecated_field,
    ALGORITHM=INPLACE,
    LOCK=NONE;
-- → サポート外操作だとエラー、明示することで意図しないテーブルコピーを防止

pt-online-schema-change / gh-ost (オンライン ALTER)

外部ツールを検討する場合も、対応製品/版/FK/trigger/binlog/負荷・切替の条件を確認します。自動的な導入/実行手順にはしません。新テーブルを作って差分を同期しながらコピー → 切替の仕組み:

旧例の注意:実行しない旧ツール例です。gh-ostが一律により安全とは言えず、名称/切替/旧表削除等の挙動や対応条件を製品別に確認します。--executeを本番へ追加しません。

# Percona Toolkit の pt-online-schema-change
pt-online-schema-change \
  --alter "ADD COLUMN email VARCHAR(255)" \
  D=mydb,t=users \
  --execute

# GitHub の gh-ost (バイナリログベース、より安全)
gh-ost \
  --database=mydb --table=users \
  --alter="ADD COLUMN email VARCHAR(255)" \
  --execute

# どちらも:
# 1. _users_new という新テーブル作成
# 2. 差分をトリガー or binlog で同期
# 3. 完了後 RENAME で切替
# 4. 旧テーブルを削除

ローリングデプロイの順序

本番運用中のサービスでカラムを変更する場合、アプリと DB の互換性を保つ必要があります。鉄則は「追加は先、削除は後」:

カラム追加

  1. DB 側: 新カラム追加 NULL/DEFAULTと旧writerの列指定・位置依存等を確認し、全旧アプリの書込成功を保証しない
  2. アプリデプロイ: 新カラムを読み書きするコード
  3. 必要ならバックフィル (既存行に値を埋める UPDATE)
  4. NOT NULL 化が必要なら、ここで ALTER ... NOT NULL

カラム削除

  1. アプリデプロイ: そのカラムを読み書きしないコードに変更
  2. 全writer/読取/外部連携と切戻し・保持条件を確認。固定の日数だけで削除を判断しない
  3. DB 側: カラム削除

旧例の注意:NG/OKの比較記録です。旧列を読まないアプリ1本の配備だけで、全writer/依存/復旧の条件が満たされる意味ではありません。削除をそのまま実行しません。

-- ❌ NG: アプリより先に DB のカラムを消す
ALTER TABLE users DROP COLUMN email;
-- → 旧バージョンのアプリが SELECT email でエラー連発

-- ✅ OK: アプリを先に「email を読まない」に更新してから DB DROP
-- (Blue/Green デプロイ・カナリアでも同様)

Laravel マイグレーションでの例

旧例の注意:旧LaravelのDBAL必須を全版へ適用しません。downのDROPはデータを戻さず、FK/索引/属性/実名と各別例を確認します。CWikiでmigrateを実行しません。

// php artisan make:migration add_email_to_users_table
public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('email', 255)->nullable()->after('name');
        $table->index('email');
    });
}

public function down(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropIndex(['email']);
        $table->dropColumn('email');
    });
}

// 複数カラム削除
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['phone', 'fax']);
});

// 型変更 (要 doctrine/dbal)
Schema::table('users', function (Blueprint $table) {
    $table->string('name', 200)->nullable(false)->change();
});

FAQ

Q: NOT NULL カラムを既存テーブルに追加できない
A: 型/既存値/モード/後続INSERTを確認します。検体ではDEFAULTなしのINT NOT NULL追加は既存行が0となり、後の省略INSERTは1364でした。0等を業務上の既定として一律採用せず、NULLや値の意味から設計します。

Q: ALTER TABLE が終わらない
A: 状態/待ちと数値進捗を区別します。innodb_alter_table_progress表は今回の両検体には存在せず、MySQLのPerformance SchemaとMariaDBの進捗情報には別の条件があります。KILLや外部ツール切替を自動対処にしません。

Q: カラム削除すると DB ファイルサイズが減らない
A: 型/engine/表領域/アルゴリズムで空き領域とファイルへの反映は異なります。容量回収が本当に必要か、再編成のロック/一時領域/負荷を確認し、OPTIMIZEやVACUUM FULLを通常の削除完了条件として追加しません。

元の色付きADDとDROPの記録

本稿はmysqlのカラム追加/削除方法に関する記事です。

 

カラム追加

元例の注意:元の色付きADD例です。int(20)/AFTER/NOT NULL/DEFAULTの条件は対象版と現在の型・業務仕様に合わせて確認し、実在account_tableへ試しません。

ALTER TABLE account_table ADD COLUMN account_type int(20) NOT NULL DEFAULT 0 AFTER account_id;

 

カラム削除

元例の注意:元の色付きDROP例です。列の値を失い、ROLLBACKや同名列の再追加で戻せるとは限りません。実表へ実行しません。

ALTER TABLE account_table DROP COLUMN account_type;

確認範囲と関連ページ

2026年10月26日確認。新13SQLファイルを新規tmpfs MySQL8.4.13/MariaDB11.8.9-ubu2404で実行し各17/計34試験に成功。追加と既存値/後続INSERT、MDL、削除と再追加/ROLLBACK/view、IF EXISTS、監視catalog、FK実名/参照切れ/NULL/RESTRICT/索引/再追加を検査。CASCADE/SET NULLと無効中の参照切れ/再有効化も別の負の検査で確かめました。無効化の新しい実行SQLを記事へ追加していません。本番DDL/データ/設定/計測機能/アプリや既存認証は変更していません。全製品/版/型/engine/複合FK/大規模負荷・無停止/復旧、PG/旧全SQL/Laravel/外部ツールの実行保証ではありません。新2図の900/360px静止画で文字枠を確認し、Work設定でブラウザ無効のため実ブラウザ/実端末は未検証です。

テーブル関連の親ページから子ページを選択できます。カラム追加/削除、外部キー、作成、定義変更、名前変更、製品配下のSQL、MySQL/MariaDBも参照してください。

一次資料:MySQL ALTERの構文/属性、MariaDB ALTERとIF EXISTS、MySQL FKと再有効化時の非再検査、Online DDLとmetadata lock、Performance SchemaでのALTER計測条件、MariaDBのPROCESSLIST/進捗、PG SET CONSTRAINTS、PGの参照アクション、Laravel13の列/外部キー、ptの書込み/切替/dry-run、gh-ostの要件/制約。

子ページ

子ページはありません

同階層のページ
  1. テーブル作成
  2. カラムの定義変更
  3. カラム名の変更
  4. カラム追加 / 削除
  5. インデックスの作成
  6. インデックスの削除
  7. AUTO_INCREMENTの仕様について
  8. AUTO_INCREMENTの追加と確認方法
  9. 外部キーの追加/削除と制約名の確認方法