◀ 2.

MySQL/MariaDBのカラム定義変更|MODIFYの属性保持とロック

▶

MODIFYは型だけでなく、残したい属性を確認する

定義変更で残す属性と利用側を別に確認する図

MySQL/MariaDBのMODIFYやCHANGEでは、変更後に必要な列属性を指定します。型だけを広げたつもりでも、NOT NULL/DEFAULT/COMMENT/UNSIGNED等を省略すると属性を失う場合があります。索引の定義は別に扱い、列名だけを変えるRENAME COLUMNと混同しません。

1. 変更前の定義と値を把握する

SHOW CREATE TABLE cwiki_w24.items;
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT,
       CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'cwiki_w24' AND TABLE_NAME = 'items'
ORDER BY ORDINAL_POSITION;

新規隔離検体のitemsを読む例です。型・NULL・既定値・コメント・charset/collationだけでなく、SHOW CREATE TABLEの索引/制約も確認します。実表の件数や最大値・NULL件数の調査は負荷と実行権限を考慮してください。EXPLAINの推定行数を正確な全件数とは扱いません。

2. 属性を明示して長さを広げる検体例

-- Preserve every intended column attribute; index definitions are separate.
ALTER TABLE cwiki_w24.items MODIFY code
    VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
    NOT NULL DEFAULT 'new' COMMENT 'public label';

検体ではvarchar(20)からvarchar(40)になり、NOT NULL/既定値/コメント/charset/collationと2行の値は保持されました。比較用の別表で型だけを指定した場合は、NULL許可になり、元の既定値/コメントが失われました。現在の定義を読まず、この指定を任意の別表へ流用しません。

値が長さを超える縮小は、strictモードの検体では両製品とも1265で拒否され、長い元値とvarchar(40)は残りました。エラーを消すためにstrictを外す・値を切る・文字を除去する対処にしません。変換/丸め/欠損が許されるか、業務仕様を先に決めます。

3. NULLを禁止する前に意味を確認する

NULLを含む別の検体表をNOT NULLに変えると、MySQL1138/MariaDB1265で拒否されました。だからといって一律に空文字や0で埋める手順にはしません。未知/未入力/該当なしの区別、アプリの書込み経路、既定値の意味、並行更新を調べます。既存NULLの置換はデータ変更であり、DDLの前提を作るだけの無害な操作ではありません。

4. DEFAULT変更と既存行の更新は別

名前をlabelへ変更済みの検体で、次のSQLを実行しました。

-- Affects omitted values on later inserts, not existing rows.
ALTER TABLE cwiki_w24.items ALTER COLUMN label SET DEFAULT 'queued';

既存2行の値は変わらず、後からlabelを省略して追加した検体行だけがqueuedになりました。DEFAULTを変えれば過去行の値も変わる、と考えないでください。既存行へUPDATEする場合は条件・件数・競合・取消範囲を別に設計します。

5. INSTANTでもmetadata lockを待つ

-- Fresh fixture only. INSTANT still requires metadata locks.
ALTER TABLE cwiki_w24.items ADD COLUMN metadata_probe INT, ALGORITHM=INSTANT;

同じ新規検体に対し、別接続が読取transactionを開いた状態で、DDL接続のlock_wait_timeoutを1秒に限定すると1205になりました。読取transactionをROLLBACKしてから同じADDは成功しました。1秒という検体の待ち制限を本番へ推奨するものではありません。

INSTANT/INPLACE/COPYは操作/製品/版/表の条件で選択が異なります。アルゴリズムが未対応なら指定によって拒否される場合もあります。LOCK=NONEがmetadata lockを不要にするわけではなく、実時間は検体の行数だけで本番へ外挿できません。今回は大規模負荷やcutoverの無停止を計測していません。

外部ツール・Laravel・他製品の旧例

後のpt-online-schema-change/gh-ostは、秘密値の引数直書き、root、--execute、旧表の削除、FK/trigger等に個別のリスクがあります。ptのdry-runも新表を作成/ALTERし、完全な読取だけではありません。gh-ostの公式要件には外部キー/trigger非対応などの制約があります。両ツールを全製品で使える無停止の万能策として勧めません。

Laravel13のchange()でも、残すmodifierを明示する必要があります。旧例はnameの変更とusernameからnameへの変更が同居し、同じ表の連続手順として競合する可能性もあります。DBALの必要性はLaravel/DB/操作/版で確認し、全環境へcomposer requireを追加しません。CWiki本体でmigrateを実行する手順ではありません。

PostgreSQL/SQL Server/Oracleの旧比較は区分して保持します。PostgreSQLのDEFAULT付き列追加が書換えを省略できる条件と、全ALTERが即時/ロック不要なことは別です。既存データを旧column削除で失う段階移行も、最も安全と一律に断定せず、同期と検証・切戻しの条件を設計します。

旧例の注意:無停止の保証ではありません。通常のDDLはDMLと同じROLLBACKで取り消せるとは限りません。

この記事の要点
  • MySQL/MariaDB: ALTER TABLE t MODIFY col VARCHAR(255)
  • PostgreSQL: ALTER TABLE t ALTER COLUMN col TYPE VARCHAR(255)
  • リネーム: MySQL CHANGE old_name new_name 型 / PG RENAME COLUMN
  • デフォルト値: SET DEFAULT / DROP DEFAULT
  • 巨大テーブルはオンラインDDL/外部ツールでもmetadata lock/切替待ち/対象条件があり、停止時間ゼロと保証しない

MySQL での ALTER TABLE

型・属性の変更(MODIFY)

旧例の注意:型だけの指定でNULL/DEFAULT/COMMENT/charset等を落とす場合があります。位置/文字コード変更も別の影響を確認します。

-- 型変更
ALTER TABLE users MODIFY name VARCHAR(255);

-- 型 + NOT NULL + DEFAULT 一括
ALTER TABLE users MODIFY name VARCHAR(255) NOT NULL DEFAULT '';

-- カラム位置移動
ALTER TABLE users MODIFY name VARCHAR(255) AFTER id;
ALTER TABLE users MODIFY name VARCHAR(255) FIRST;

-- 文字コード変更
ALTER TABLE users MODIFY name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

リネーム + 型変更(CHANGE)

旧例の注意:複数の別例です。旧名/新名の競合、属性保持、DROPのデータ損失を調べ、同じusersへ順次実行しません。

-- 名前を変える時は CHANGE
ALTER TABLE users CHANGE username name VARCHAR(255) NOT NULL;
-- old_name new_name 型 ... の順

-- MySQL 8.0+ は RENAME COLUMN もサポート
ALTER TABLE users RENAME COLUMN username TO name;

-- 複数まとめて変更
ALTER TABLE users
    MODIFY name VARCHAR(255) NOT NULL,
    CHANGE email mail VARCHAR(255) NOT NULL,
    ADD COLUMN phone VARCHAR(20),
    DROP COLUMN deleted_at;

デフォルト値の変更

旧例の注意:既存NULLのUPDATEは業務データ変更です。意味や並行書込を調べずactiveで埋めません。DEFAULT変更だけで既存行は変わりません。

-- デフォルト値追加
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';

-- デフォルト値削除
ALTER TABLE users ALTER COLUMN status DROP DEFAULT;

-- NOT NULL 追加(既存 NULL があるとエラー → 先に UPDATE で埋める)
UPDATE users SET status = 'active' WHERE status IS NULL;
ALTER TABLE users MODIFY status VARCHAR(20) NOT NULL DEFAULT 'active';

-- NOT NULL 削除
ALTER TABLE users MODIFY status VARCHAR(20) NULL;

PostgreSQL での ALTER TABLE

旧例の注意:PostgreSQL用の比較です。変換式/時刻/TZ/NULL/ロック等を個別検証し、MySQLへ流用しません。

-- 型変更
ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(255);

-- USING で明示変換が必要なケース
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING age::INTEGER;
ALTER TABLE logs ALTER COLUMN created_at TYPE TIMESTAMPTZ
    USING created_at AT TIME ZONE 'UTC';

-- リネーム
ALTER TABLE users RENAME COLUMN username TO name;

-- デフォルト値
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';
ALTER TABLE users ALTER COLUMN status DROP DEFAULT;

-- NOT NULL
ALTER TABLE users ALTER COLUMN status SET NOT NULL;
ALTER TABLE users ALTER COLUMN status DROP NOT NULL;

-- 一気に複数操作
ALTER TABLE users
    ALTER COLUMN name TYPE VARCHAR(255),
    ALTER COLUMN status SET DEFAULT 'active',
    ALTER COLUMN status SET NOT NULL;

SQL Server / Oracle

旧例の注意:製品別の断片です。SQL ServerのDEFAULT制約とOracleのDEFAULT NULLを共通の削除処理とみなしません。

-- SQL Server
ALTER TABLE users ALTER COLUMN name VARCHAR(255) NOT NULL;
EXEC sp_rename 'users.username', 'name', 'COLUMN';
ALTER TABLE users ADD CONSTRAINT df_status DEFAULT 'active' FOR status;

-- Oracle
ALTER TABLE users MODIFY (name VARCHAR2(255) NOT NULL);
ALTER TABLE users RENAME COLUMN username TO name;
ALTER TABLE users MODIFY (status DEFAULT 'active');
ALTER TABLE users MODIFY (status DEFAULT NULL);   -- DEFAULT 削除

カラム追加・削除

旧例の注意:同名phone追加の別例を連続実行しません。DROPやdefault/NOT NULLの影響を実表へ試しません。

-- 追加
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email;    -- MySQL
ALTER TABLE users ADD COLUMN phone VARCHAR(20) DEFAULT NULL;

-- 削除
ALTER TABLE users DROP COLUMN phone;

-- 複数追加(MySQL)
ALTER TABLE users
    ADD COLUMN phone VARCHAR(20),
    ADD COLUMN address TEXT,
    ADD COLUMN birthdate DATE;

巨大テーブルでの問題点

10 億行あるテーブルで ALTER TABLE を実行すると:

  • 実時間は操作/版/データ/索引/負荷/待ちに依存し、行数だけで固定しない
  • 許可される並行操作とmetadata lockを製品/操作/アルゴリズム別に確認
  • 失敗するとロールバックでさらに時間
  • レプリケーション遅延

MySQL Online DDL の進化

旧例の注意:版の概要で、全操作/全engineの無停止保証ではありません。現行の操作別表とmetadata lockを確認します。

バージョン状況
5.5 以前旧版の参考。全操作で読書とも停止するという一律の判定ではない
5.6Online DDL 導入。インデックス追加等は読み書き継続可能
5.7Online DDL 対象拡大
8.0対応操作/条件でのINSTANT追加。metadata lock待ちがなくなるわけではない
8.0.29+対応範囲の参考。ADD/DROPの拡張時期とRENAMEの条件を別に確認

旧例の注意:数ミリ秒/最確実/全操作で並行可という旧コメントは保証できません。同名flagの2例も順次実行せず、未対応のALGORITHM/LOCKは失敗する場合があります。

-- MySQL 8.0+ の INSTANT は数ミリ秒で完了
ALTER TABLE big_table ADD COLUMN flag TINYINT, ALGORITHM=INSTANT;

-- アルゴリズムを明示
-- ALGORITHM=COPY     - 旧式、全行コピー(最遅、最確実)
-- ALGORITHM=INPLACE  - 既存テーブルを変更、読み書き継続可(多くの場合)
-- ALGORITHM=INSTANT  - メタデータのみ変更、即時完了(限定操作)

-- LOCK レベル
-- LOCK=NONE      - 読み書きとも継続可
-- LOCK=SHARED    - 読みのみ可
-- LOCK=EXCLUSIVE - 全停止

ALTER TABLE big_table
    ADD COLUMN flag TINYINT,
    ALGORITHM=INPLACE, LOCK=NONE;

pt-online-schema-change(Percona Toolkit)

外部ツールを検討する場合は各製品/版/操作の対応と制約・負荷・切替計画を確認します。型変換だから必ず導入する手順ではありません。

旧例の注意:実行しない旧例です。root/秘密値の引数直書き、--execute、旧表削除やFK切替に影響があります。dry-runも新表のCREATE/ALTERを伴うため読取専用ではありません。

# 仕組み: 新テーブルを作る → トリガで同期 → 旧データをコピー → アトミック RENAME

# インストール (Ubuntu)
sudo apt install percona-toolkit

# ALTER 実行
pt-online-schema-change \
  --alter "MODIFY name VARCHAR(500) NOT NULL" \
  --execute \
  h=localhost,D=mydb,t=users,u=root,p=secret

# dry-run(実行しない確認のみ)
pt-online-schema-change \
  --alter "MODIFY name VARCHAR(500) NOT NULL" \
  --dry-run \
  h=localhost,D=mydb,t=users

# 注意:
#   外部キーで参照されているテーブルは制約処理が複雑
#   --alter-foreign-keys-method=auto / drop_swap / rebuild_constraints

gh-ost(GitHub 製)

pt-online-schema-change の代替。トリガを使わずbinlog を読んで同期する方式ですが、負荷や切替待ち/FK/trigger等の制限を確認します。無影響やptより低負荷と一律には保証しません:

旧例の注意:実行しない旧例です。秘密値/--execute/replication等の要件を調べ、FK/trigger等の非対応を無視しません。省略記号の行は実コマンドではありません。

gh-ost \
  --user=root --password=secret \
  --host=localhost \
  --database=mydb \
  --table=users \
  --alter="MODIFY name VARCHAR(500) NOT NULL" \
  --execute

# dry-run
gh-ost ... --execute=false

# レプリカでスロットリング自動制御
gh-ost ... --max-lag-millis=1500

PostgreSQL での巨大テーブル変更

旧例の注意:PostgreSQLの全ALTERがinstantではありません。volatile defaultやロック等の条件を確認し、旧column削除を安全策として無条件に実行しません。

-- PostgreSQL 11+ : 多くの ALTER がインスタント完了
-- DEFAULT 追加もテーブル書換不要
ALTER TABLE big_table ADD COLUMN flag BOOLEAN DEFAULT FALSE NOT NULL;

-- 型変更で全行書換が起きるケース(重い)
ALTER TABLE big_table ALTER COLUMN id TYPE BIGINT;
-- → テーブル全コピー、ACCESS EXCLUSIVE ロック

-- 安全策: 一時カラム経由
ALTER TABLE big_table ADD COLUMN id_new BIGINT;
UPDATE big_table SET id_new = id WHERE id_new IS NULL;  -- 分割実行
-- ... トリガで新規 INSERT も同期 ...
ALTER TABLE big_table DROP COLUMN id;
ALTER TABLE big_table RENAME COLUMN id_new TO id;

-- pg_repack でテーブル再構成(要拡張)
-- pg_repack -t big_table -d mydb

Laravel マイグレーションでの変更

旧例の注意:旧Laravelの依存追加を全版へ適用しません。残すmodifierとname/usernameの競合、dropの影響を調べ、CWikiでmigrateを実行しません。

// doctrine/dbal が必要
// composer require doctrine/dbal

Schema::table('users', function (Blueprint $table) {
    $table->string('name', 255)->nullable()->change();      // 型・NULL 変更
    $table->renameColumn('username', 'name');                // リネーム
    $table->string('status')->default('active')->change();   // デフォルト値
    $table->dropColumn('deleted_at');
});

変更前のチェックリスト

  • 実行計画の推定と正確な件数を区別し、調査負荷/操作/型/索引/待ちから検証条件を設計する
  • レプリケーション遅延が許容範囲か
  • 外部キーが絡む場合は手順を整理
  • 失敗時のロールバック計画
  • NOT NULLの前にNULLの意味とwriter/移行条件を確認し、一律の値埋めやDEFAULT指定で解消しない
  • 本番投入前にステージング環境で時間計測

FAQ

Q: ALTER TABLE が終わらない
A: SHOW PROCESSLIST等で状態/待ちを調べますが、全DDLの数値進捗とは限りません。KILLは対象/影響/取消処理の時間等を確認した別判断で、自動対処にしません。

Q: NOT NULL を後から追加してエラー
A: NULLを含むか、対象定義/モード等を確認します。旧UPDATE例の一律置換はせず、NULLの意味と利用側/競合を確認して変更計画を立てます。

Q: カラム順序を変えたい(MySQL)
A: MODIFY col 型 AFTER 他のカラム または FIRST。MariaDBにも対応があります。PostgreSQLの同じ直接構文とは区別し、順序だけで実表を再構成する必要性も確認します。

Q: 巨大テーブルで型変更を確実に安全に
A: 新カラム追加 → トリガ / アプリで二重書き込み → バッチで埋め戻し → 旧カラム削除、という方法もありますが、同期/並行writer/照合/削除条件を設計しないと安全ではありません。万能な最安全策とは扱いません。

元のMariaDB MODIFYの例

元例の注意:元のMariaDB用途と書式付きMODIFY例です。longtext/NOT NULL以外の残す属性や既存値も先に確認し、NULLを自動的に埋めたり実表へこの例を試したりしません。

これはMariaDBのカラム定義の変更に関する記事です。

 

【構文】

ALTER TABLE テーブル名 MODIFY カラム定義;

 

【例】

ALTER TABLE test_table MODIFY col1 longtext NOT NULL;

 

【説明】

test_tableテーブルのcol1カラムの定義を変更しています。

データ型はlongtextでNOT NULL制約を付与しています。

 

確認範囲と関連ページ

2026年10月26日確認。新12SQLを新規tmpfs MySQL8.4.13/MariaDB11.8.9-ubu2404で実行し各15/計30試験に成功。既存表/複製、列の属性/値、strictな縮小とNULL拒否、名前変更とview/索引、DEFAULT、metadata lock待ち、型/日時の製品差と外部キーを検査しました。本番schema/データ/設定/利用側の移行はしていません。既存のvendor/依存を変更せず、Laravelのmigrate/外部オンライン変更ツール、他DB製品の旧全SQL、全型/照合順序/依存/巨大負荷・無停止や完全復旧を実行保証するものではありません。新図は900/360px静止画で確認し、Work設定でブラウザ無効のため実ブラウザ/実端末は未検証です。

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

一次資料:MySQL ALTERの属性と依存、MariaDB ALTER、MySQL LIKE、MySQL SELECTでの作成、Online DDLとmetadata lock、操作別のOnline DDL、MySQLの格納容量、MariaDB TIMESTAMPの版別範囲、MariaDB JSON、SQL Serverの列名変更、PostgreSQL ALTER、Laravel13の列変更、Laravel10.x公式文書の旧DB条件、pt-online-schema-changeの動作とdry-run、gh-ostの要件/制約。

子ページ

子ページはありません

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