追加と削除は、戻せる範囲も含めて別に設計する
カラム追加・削除では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とデータ/アプリ変更を分けます。
| この記事の要点 |
|
カラム追加 (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 の互換性を保つ必要があります。鉄則は「追加は先、削除は後」:
カラム追加
- DB 側: 新カラム追加 NULL/DEFAULTと旧writerの列指定・位置依存等を確認し、全旧アプリの書込成功を保証しない
- アプリデプロイ: 新カラムを読み書きするコード
- 必要ならバックフィル (既存行に値を埋める UPDATE)
- NOT NULL 化が必要なら、ここで
ALTER ... NOT NULL
カラム削除
- アプリデプロイ: そのカラムを読み書きしないコードに変更
- 全writer/読取/外部連携と切戻し・保持条件を確認。固定の日数だけで削除を判断しない
- 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 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- MySQL/MariaDBのテーブル一覧|表・ビュー・権限と絞り込み 2026-10-26 07:32:39
- MySQL/MariaDBのDB選択|USEと接続・既定DBの違い 2026-10-26 07:32:39
- MySQL/MariaDBのDB一覧|SHOW DATABASESと権限の違い 2026-10-26 07:32:39
- MySQLへの接続|接続先・認証・TLSとエラーの確認 2026-10-26 07:32:39
- MySQL/MariaDBのカラム定義確認|権限・既定値・ORMとの違い 2026-10-26 06:18:53
- MySQL/MariaDBのメタデータ|列・索引・制約の確認ガイド 2026-10-26 06:18:53
- MySQL/MariaDBのテーブル定義確認|DESC・SHOW CREATEと復旧の範囲 2026-10-26 06:18:53
- MySQL/MariaDBのAUTO_INCREMENT追加・確認|既存IDと属性の保持 2026-10-26 05:42:11
- MySQL/MariaDBのAUTO_INCREMENT|欠番・取得ID・カウンタの違い 2026-10-26 05:42:11
- MySQL/MariaDBの索引削除|不可視化・UNIQUE・外部キーの注意 2026-10-26 05:11:35
- MySQL/MariaDBの索引作成|結果比較・UNIQUE・実行計画の確認 2026-10-26 05:11:35
- MySQL/MariaDBの外部キー|制約名・参照切れ・削除と再検査 2026-10-26 04:51:08
- MySQL/MariaDBのカラム追加・削除|既存値・互換性・ロックの確認 2026-10-26 04:51:08
- MySQL/MariaDBのカラム名変更|RENAME COLUMNと利用側の確認 2026-10-26 04:24:55
- MySQL/MariaDBのカラム定義変更|MODIFYの属性保持とロック 2026-10-26 04:24:55