はじめに#
データベーススキーマは、アプリケーションの成長とともに変化し続けます。新機能の追加によるカラムの追加、パフォーマンス改善のためのデータ型変更、不要になったカラムの削除など、本番環境で稼働しているデータベースに対して安全にスキーマ変更を行う能力は、現代のソフトウェア開発において不可欠なスキルです。
本記事では、PostgreSQLにおけるALTER TABLEの基本操作から、マイグレーションツール(Flyway/Liquibase/Prisma Migrate)を活用した体系的なスキーマ管理、そして本番環境でのスキーマ変更における注意点まで、実践的なサンプルコードとともに解説します。
この記事を読むことで、以下のことができるようになります。
- ALTER TABLEを使ってカラムの追加・削除・型変更ができる
- 制約(NOT NULL/DEFAULT/CHECK等)を安全に変更できる
- マイグレーションツールを使ってスキーマ変更を体系的に管理できる
- 本番環境でのスキーマ変更におけるリスクと対策を理解できる
- ダウンタイムを最小化するスキーマ変更戦略を実践できる
前提条件#
- PostgreSQL 14以降がインストールされていること
- psqlまたはGUIツール(pgAdmin、DBeaver等)でデータベースに接続できること
- CREATE TABLE文とテーブル設計の基本を理解していること
- トランザクション(BEGIN/COMMIT/ROLLBACK)の概念を理解していること
サンプルデータの準備#
本記事で使用するサンプルテーブルを準備します。以下のSQLを実行してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-- ユーザーテーブルの作成
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 商品テーブルの作成
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price INTEGER NOT NULL,
stock INTEGER DEFAULT 0
);
-- サンプルデータの挿入
INSERT INTO users (email, name) VALUES
('tanaka@example.com', '田中太郎'),
('suzuki@example.com', '鈴木花子'),
('yamada@example.com', '山田一郎');
INSERT INTO products (name, price, stock) VALUES
('ノートPC', 120000, 25),
('マウス', 3500, 150),
('キーボード', 8000, 80);
|
ALTER TABLEの基本構文#
ALTER TABLEは、既存のテーブル構造を変更するためのDDL(Data Definition Language)文です。PostgreSQLでは、カラムの追加・削除・変更、制約の追加・削除、テーブル名やカラム名の変更など、多彩な操作が可能です。
基本的な構文パターン#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
-- カラムの追加
ALTER TABLE テーブル名 ADD COLUMN カラム名 データ型 [制約];
-- カラムの削除
ALTER TABLE テーブル名 DROP COLUMN カラム名;
-- カラムのデータ型変更
ALTER TABLE テーブル名 ALTER COLUMN カラム名 TYPE 新しいデータ型;
-- カラム名の変更
ALTER TABLE テーブル名 RENAME COLUMN 旧カラム名 TO 新カラム名;
-- テーブル名の変更
ALTER TABLE 旧テーブル名 RENAME TO 新テーブル名;
-- 制約の追加
ALTER TABLE テーブル名 ADD CONSTRAINT 制約名 制約定義;
-- 制約の削除
ALTER TABLE テーブル名 DROP CONSTRAINT 制約名;
|
カラムの追加#
基本的なカラム追加#
既存のテーブルに新しいカラムを追加する最も基本的な例です。
1
2
|
-- usersテーブルに電話番号カラムを追加
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
|
このコマンドを実行すると、既存の全行に対してNULL値が設定されます。
1
2
|
-- 追加結果の確認
SELECT * FROM users;
|
デフォルト値付きのカラム追加#
カラム追加時にデフォルト値を指定すると、既存行にはそのデフォルト値が設定されます。
1
2
3
4
5
|
-- 有効フラグカラムをデフォルト値trueで追加
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT TRUE;
-- 結果確認
SELECT user_id, name, is_active FROM users;
|
| user_id |
name |
is_active |
| 1 |
田中太郎 |
true |
| 2 |
鈴木花子 |
true |
| 3 |
山田一郎 |
true |
NOT NULL制約付きカラムの追加#
NOT NULL制約付きのカラムを追加する場合、既存行に対してNULLを設定できないため、DEFAULTの指定が必須です。
1
2
3
4
5
6
|
-- NG: デフォルト値なしでNOT NULLカラムを追加しようとするとエラー
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL;
-- ERROR: column "status" of relation "users" contains null values
-- OK: デフォルト値を指定してNOT NULLカラムを追加
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
|
複数カラムの同時追加#
PostgreSQLでは、複数のALTER操作を1つのコマンドで実行できます。これにより、テーブルロックの取得回数を減らし、パフォーマンスを向上させることができます。
1
2
3
4
|
-- 複数カラムを同時に追加
ALTER TABLE users
ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN last_login_at TIMESTAMP;
|
カラムの削除#
基本的なカラム削除#
不要になったカラムを削除します。
1
2
|
-- phoneカラムを削除
ALTER TABLE users DROP COLUMN phone;
|
IF EXISTSオプション#
カラムが存在しない場合にエラーを発生させたくない場合は、IF EXISTSオプションを使用します。
1
2
3
|
-- カラムが存在する場合のみ削除
ALTER TABLE users DROP COLUMN IF EXISTS phone;
-- NOTICE: column "phone" of relation "users" does not exist, skipping
|
CASCADE オプション#
削除対象のカラムがビューや他のオブジェクトから参照されている場合、CASCADEオプションを指定することで依存オブジェクトごと削除できます。
1
2
|
-- 依存オブジェクトがあるカラムを削除
ALTER TABLE users DROP COLUMN status CASCADE;
|
ただし、CASCADEは意図しないオブジェクトも削除する可能性があるため、本番環境では慎重に使用してください。
データ型の変更#
互換性のある型変更#
暗黙的な型変換が可能な場合、単純なTYPE指定で変更できます。
1
2
3
4
5
|
-- VARCHARの長さを拡張
ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(200);
-- INTEGERからBIGINTへの変更
ALTER TABLE products ALTER COLUMN price TYPE BIGINT;
|
USING句による型変換#
暗黙的な変換ができない場合は、USING句で変換式を指定します。
1
2
3
4
5
6
7
8
9
10
11
12
|
-- 文字列型の価格カラムを整数型に変換
-- 事前準備:テスト用テーブル
CREATE TABLE test_products (
id SERIAL PRIMARY KEY,
price_text VARCHAR(20)
);
INSERT INTO test_products (price_text) VALUES ('1000'), ('2500'), ('500');
-- USING句で変換式を指定
ALTER TABLE test_products
ALTER COLUMN price_text TYPE INTEGER
USING price_text::INTEGER;
|
型変更時の注意点#
データ型の変更は、テーブル全体の書き換えが発生する場合があります。特に大規模なテーブルでは、以下の点に注意が必要です。
- ロック: ALTER TABLE中はテーブルに排他ロック(ACCESS EXCLUSIVE)がかかります
- 変換エラー: 既存データが新しい型に変換できない場合はエラーになります
- ディスク容量: テーブル書き換え中は一時的に2倍のディスク容量が必要です
1
2
3
4
|
-- 安全な型変更の前に、変換可能かどうかを確認
SELECT price_text, price_text::INTEGER
FROM test_products
WHERE price_text !~ '^\d+$';
|
制約の変更#
NOT NULL制約の追加と削除#
1
2
3
4
5
|
-- NOT NULL制約を追加
ALTER TABLE users ALTER COLUMN name SET NOT NULL;
-- NOT NULL制約を削除
ALTER TABLE users ALTER COLUMN name DROP NOT NULL;
|
NOT NULL制約を追加する際、既存行にNULL値が含まれているとエラーになります。
1
2
3
4
5
6
7
|
-- NULL値を含むカラムにNOT NULLを追加しようとするとエラー
ALTER TABLE users ALTER COLUMN last_login_at SET NOT NULL;
-- ERROR: column "last_login_at" of relation "users" contains null values
-- 先にNULL値を更新してから制約を追加
UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE last_login_at IS NULL;
ALTER TABLE users ALTER COLUMN last_login_at SET NOT NULL;
|
DEFAULT値の設定と削除#
1
2
3
4
5
|
-- デフォルト値を設定
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';
-- デフォルト値を削除
ALTER TABLE users ALTER COLUMN status DROP DEFAULT;
|
CHECK制約の追加#
1
2
3
4
5
6
7
8
9
|
-- 価格が0以上であることを保証するCHECK制約を追加
ALTER TABLE products
ADD CONSTRAINT products_price_positive
CHECK (price >= 0);
-- 複合CHECK制約
ALTER TABLE products
ADD CONSTRAINT products_stock_valid
CHECK (stock >= 0 AND stock <= 10000);
|
UNIQUE制約の追加#
1
2
3
4
|
-- 商品名のユニーク制約を追加
ALTER TABLE products
ADD CONSTRAINT products_name_unique
UNIQUE (name);
|
外部キー制約の追加#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-- 注文テーブルの作成と外部キー制約の追加
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INTEGER,
product_id INTEGER,
quantity INTEGER NOT NULL,
order_date DATE DEFAULT CURRENT_DATE
);
-- 外部キー制約を後から追加
ALTER TABLE orders
ADD CONSTRAINT orders_user_fk
FOREIGN KEY (user_id) REFERENCES users(user_id);
ALTER TABLE orders
ADD CONSTRAINT orders_product_fk
FOREIGN KEY (product_id) REFERENCES products(product_id);
|
制約の削除#
1
2
3
4
5
6
7
|
-- 制約名を指定して削除
ALTER TABLE products DROP CONSTRAINT products_price_positive;
-- 制約名が不明な場合は、システムカタログから確認
SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'products'::regclass;
|
カラム名とテーブル名の変更#
カラム名の変更#
1
2
3
4
5
|
-- カラム名を変更
ALTER TABLE users RENAME COLUMN name TO full_name;
-- 確認
\d users
|
テーブル名の変更#
1
2
3
4
5
|
-- テーブル名を変更
ALTER TABLE products RENAME TO items;
-- 元に戻す
ALTER TABLE items RENAME TO products;
|
カラム名やテーブル名を変更すると、それを参照しているビュー、関数、アプリケーションコードも更新が必要になります。変更前に影響範囲を十分に調査してください。
安全なスキーマ変更のパターン#
本番環境でスキーマ変更を行う際は、ダウンタイムを最小化し、ロールバック可能な状態を維持することが重要です。以下に、安全なスキーマ変更のパターンを紹介します。
NOT VALID制約と段階的検証#
大規模テーブルに制約を追加する場合、NOT VALIDオプションを使用することで、既存データの検証をスキップし、ロック時間を短縮できます。
1
2
3
4
5
6
7
8
|
-- Step 1: NOT VALIDで制約を追加(既存データの検証をスキップ)
ALTER TABLE products
ADD CONSTRAINT products_price_check
CHECK (price > 0)
NOT VALID;
-- Step 2: バックグラウンドで制約を検証(SHARE UPDATE EXCLUSIVEロック)
ALTER TABLE products VALIDATE CONSTRAINT products_price_check;
|
NOT VALIDで追加した制約は、新規INSERT/UPDATEには適用されますが、既存データは検証されません。VALIDATE CONSTRAINTを実行することで、既存データの検証を行い、制約を完全に有効化します。
CONCURRENTLYインデックス作成#
通常のCREATE INDEXは排他ロックを取得しますが、CONCURRENTLYオプションを使用すると、テーブルへの読み書きをブロックせずにインデックスを作成できます。
1
2
3
4
5
|
-- 通常のインデックス作成(テーブルがロックされる)
CREATE INDEX products_name_idx ON products(name);
-- CONCURRENTLY オプション(読み書きをブロックしない)
CREATE INDEX CONCURRENTLY products_price_idx ON products(price);
|
CONCURRENTLYオプションの注意点は以下の通りです。
- トランザクション内では使用できない
- 作成に通常より時間がかかる
- 失敗した場合、無効なインデックスが残る可能性がある
1
2
3
4
5
6
7
8
|
-- 無効なインデックスの確認と削除
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'products' AND indexdef IS NULL;
-- 無効なインデックスを削除して再作成
DROP INDEX CONCURRENTLY products_price_idx;
CREATE INDEX CONCURRENTLY products_price_idx ON products(price);
|
段階的なスキーマ変更#
大きなスキーマ変更を一度に行うのではなく、複数の小さな変更に分割して段階的に適用します。
flowchart LR
A[Step 1<br/>新カラム追加<br/>NULL許可] --> B[Step 2<br/>アプリで<br/>両カラム対応]
B --> C[Step 3<br/>データ移行]
C --> D[Step 4<br/>NOT NULL追加]
D --> E[Step 5<br/>旧カラム削除]具体的な例として、emailカラムをemailsテーブルに分離する場合を考えます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-- Step 1: 新しいテーブルを作成
CREATE TABLE user_emails (
email_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(user_id),
email VARCHAR(255) NOT NULL,
is_primary BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Step 2: データを移行
INSERT INTO user_emails (user_id, email, is_primary)
SELECT user_id, email, TRUE FROM users;
-- Step 3: アプリケーションを新テーブル対応に更新
-- Step 4: 旧カラムを削除
ALTER TABLE users DROP COLUMN email;
|
マイグレーションツールの活用#
手動でのスキーマ変更は、複数環境間での整合性維持やロールバックが困難です。マイグレーションツールを使用することで、スキーマ変更をバージョン管理し、再現可能な形で適用できます。
マイグレーションツールの比較#
| ツール |
特徴 |
対象言語/フレームワーク |
| Flyway |
SQLベース、シンプル |
Java/Spring Boot、汎用 |
| Liquibase |
XML/YAML/JSON/SQL対応、ロールバック機能 |
Java/Spring Boot、汎用 |
| Prisma Migrate |
TypeScript統合、型安全 |
Node.js/TypeScript |
| Alembic |
SQLAlchemy統合 |
Python |
| golang-migrate |
軽量、CLI中心 |
Go |
Flywayによるマイグレーション管理#
Flyway は、SQLファイルをバージョン管理してデータベースに順次適用するマイグレーションツールです。ファイル名の規約に従うだけで、バージョン管理と適用順序の制御が自動化されます。
Flywayのセットアップ#
1
2
3
4
5
6
7
8
|
# Dockerでの起動例
docker run --rm \
-v $(pwd)/migrations:/flyway/sql \
flyway/flyway \
-url=jdbc:postgresql://host.docker.internal:5432/mydb \
-user=postgres \
-password=postgres \
migrate
|
マイグレーションファイルの命名規則#
V{バージョン番号}__{説明}.sql
例:
V1__create_users_table.sql
V2__add_phone_column.sql
V3__create_orders_table.sql
マイグレーションファイルの例#
V1__create_users_table.sql:
1
2
3
4
5
6
|
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
|
V2__add_phone_column.sql:
1
2
|
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT TRUE;
|
V3__create_orders_table.sql:
1
2
3
4
5
6
7
8
9
|
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(user_id),
total_amount DECIMAL(10,2) NOT NULL,
order_date DATE DEFAULT CURRENT_DATE
);
CREATE INDEX orders_user_id_idx ON orders(user_id);
CREATE INDEX orders_order_date_idx ON orders(order_date);
|
Flyway管理テーブル#
Flywayはflyway_schema_historyテーブルで適用履歴を管理します。
1
2
3
|
SELECT version, description, installed_on, success
FROM flyway_schema_history
ORDER BY installed_rank;
|
| version |
description |
installed_on |
success |
| 1 |
create users table |
2026-01-03 10:00:00 |
true |
| 2 |
add phone column |
2026-01-03 10:05:00 |
true |
| 3 |
create orders table |
2026-01-03 10:10:00 |
true |
Liquibaseによるマイグレーション管理#
Liquibaseは、XML、YAML、JSON、SQLなど複数のフォーマットでマイグレーションを定義できるツールです。ロールバック機能が標準で提供されている点が特徴です。
Changelog(YAML形式)の例#
db.changelog-master.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
databaseChangeLog:
- changeSet:
id: 1
author: developer
changes:
- createTable:
tableName: users
columns:
- column:
name: user_id
type: serial
constraints:
primaryKey: true
- column:
name: email
type: varchar(255)
constraints:
nullable: false
unique: true
- column:
name: name
type: varchar(100)
constraints:
nullable: false
- column:
name: created_at
type: timestamp
defaultValueComputed: CURRENT_TIMESTAMP
rollback:
- dropTable:
tableName: users
- changeSet:
id: 2
author: developer
changes:
- addColumn:
tableName: users
columns:
- column:
name: phone
type: varchar(20)
- column:
name: is_active
type: boolean
defaultValueBoolean: true
rollback:
- dropColumn:
tableName: users
columnName: phone
- dropColumn:
tableName: users
columnName: is_active
|
Liquibaseのロールバック#
1
2
3
4
5
6
7
8
|
# 直近の変更を1つロールバック
liquibase rollbackCount 1
# 特定のタグまでロールバック
liquibase rollback v1.0
# ロールバックSQLのプレビュー
liquibase rollbackCountSQL 1
|
Prisma Migrateによるマイグレーション管理#
Prisma Migrateは、TypeScript/JavaScript開発者向けの型安全なマイグレーションツールです。Prismaスキーマファイルから自動的にマイグレーションを生成します。
Prismaスキーマの例#
prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
phone String?
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
orders Order[]
@@map("users")
}
model Order {
id Int @id @default(autoincrement())
userId Int @map("user_id")
totalAmount Decimal @map("total_amount") @db.Decimal(10, 2)
orderDate DateTime @default(now()) @map("order_date") @db.Date
user User @relation(fields: [userId], references: [id])
@@map("orders")
}
マイグレーションの生成と適用#
1
2
3
4
5
6
7
8
|
# マイグレーションを生成(開発環境)
npx prisma migrate dev --name add_phone_column
# マイグレーションを本番環境に適用
npx prisma migrate deploy
# マイグレーション状態の確認
npx prisma migrate status
|
生成されるマイグレーションファイル(prisma/migrations/20260103_add_phone_column/migration.sql):
1
2
|
-- AlterTable
ALTER TABLE "users" ADD COLUMN "phone" TEXT;
|
本番環境でのスキーマ変更戦略#
ダウンタイムを最小化するためのチェックリスト#
本番環境でスキーマ変更を行う前に、以下のチェックリストを確認してください。
- バックアップの取得: 変更前に必ずバックアップを取得する
- テスト環境での検証: 本番と同等のデータ量で動作確認を行う
- ロック時間の見積もり: 大規模テーブルの場合、想定されるロック時間を把握する
- ロールバック手順の準備: 問題発生時のロールバック手順を事前に準備する
- メンテナンス時間帯の選定: 影響を最小化できる時間帯を選択する
- 監視の準備: スキーマ変更中のデータベース負荷を監視する
ロック時間を最小化するテクニック#
1. 小さなバッチでのデータ更新#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
-- 大量データの更新を小さなバッチに分割
DO $$
DECLARE
batch_size INT := 10000;
affected_rows INT := 1;
BEGIN
WHILE affected_rows > 0 LOOP
UPDATE users
SET status = 'migrated'
WHERE user_id IN (
SELECT user_id FROM users
WHERE status IS NULL
LIMIT batch_size
);
GET DIAGNOSTICS affected_rows = ROW_COUNT;
COMMIT;
PERFORM pg_sleep(0.1); -- 負荷軽減のための待機
END LOOP;
END $$;
|
2. 新カラム追加とバックフィルの分離#
1
2
3
4
5
6
7
8
|
-- Step 1: NULLを許可してカラム追加(即座に完了)
ALTER TABLE users ADD COLUMN age INTEGER;
-- Step 2: バックグラウンドでデータを埋める
UPDATE users SET age = 0 WHERE age IS NULL;
-- Step 3: NOT NULL制約を追加
ALTER TABLE users ALTER COLUMN age SET NOT NULL;
|
3. テーブル置換パターン#
既存テーブルを大幅に変更する場合、新しいテーブルを作成して置き換える方法があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
-- Step 1: 新しい構造のテーブルを作成
CREATE TABLE users_new (
user_id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
full_name VARCHAR(200) NOT NULL, -- name -> full_nameに変更
phone VARCHAR(20),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Step 2: データを移行
INSERT INTO users_new (user_id, email, full_name, phone, is_active, created_at)
SELECT user_id, email, name, phone, is_active, created_at
FROM users;
-- Step 3: シーケンスを同期
SELECT setval(
pg_get_serial_sequence('users_new', 'user_id'),
(SELECT MAX(user_id) FROM users_new)
);
-- Step 4: テーブルを入れ替え(短時間のロック)
BEGIN;
ALTER TABLE users RENAME TO users_old;
ALTER TABLE users_new RENAME TO users;
COMMIT;
-- Step 5: 確認後、旧テーブルを削除
DROP TABLE users_old;
|
ブルーグリーンデプロイメントとの連携#
アプリケーションのブルーグリーンデプロイメントと連携したスキーマ変更戦略です。
sequenceDiagram
participant Blue as Blue環境<br/>(旧バージョン)
participant Green as Green環境<br/>(新バージョン)
participant DB as データベース
Note over Blue,DB: Phase 1: 両対応スキーマへ変更
DB->>DB: 新カラム追加(NULL許可)
Blue->>DB: 旧カラムで読み書き
Green->>DB: 新カラムでも読み書き可能
Note over Blue,DB: Phase 2: トラフィック切り替え
Blue->>Green: トラフィック移行
Green->>DB: 新カラムで読み書き
Note over Blue,DB: Phase 3: クリーンアップ
DB->>DB: 旧カラム削除この戦略では、スキーマ変更を「後方互換性のある変更」と「クリーンアップ」の2段階に分けることで、無停止でのスキーマ変更を実現します。
よくある失敗パターンと対策#
1. 長時間のテーブルロック#
問題: 大規模テーブルへのALTER TABLEが長時間のロックを引き起こす
対策:
- CONCURRENTLYオプション(インデックス作成時)
- NOT VALIDオプション(制約追加時)
- テーブル置換パターン
2. ディスク容量不足#
問題: ALTER TABLE中にディスク容量が不足してエラーになる
対策:
- 事前にテーブルサイズの2倍以上の空き容量を確保
- 一時テーブルスペースの利用
1
2
|
-- テーブルサイズの確認
SELECT pg_size_pretty(pg_total_relation_size('users'));
|
3. 外部キー制約違反#
問題: 参照先のデータが存在しないため、外部キー制約の追加が失敗する
対策:
- 事前にデータの整合性を確認
- 不整合データを修正してから制約を追加
1
2
3
4
5
6
7
8
9
|
-- 参照先が存在しない不整合データを検出
SELECT o.order_id, o.user_id
FROM orders o
LEFT JOIN users u ON o.user_id = u.user_id
WHERE u.user_id IS NULL;
-- 不整合データを削除または修正してから制約を追加
DELETE FROM orders WHERE user_id NOT IN (SELECT user_id FROM users);
ALTER TABLE orders ADD CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES users(user_id);
|
まとめ#
本記事では、PostgreSQLにおけるスキーマ管理とマイグレーション戦略について解説しました。
ALTER TABLEの基本操作として、カラムの追加・削除・型変更、制約の追加・削除、名前の変更などを学びました。本番環境でのスキーマ変更では、NOT VALIDオプションやCONCURRENTLYオプションを活用してロック時間を最小化することが重要です。
マイグレーションツール(Flyway、Liquibase、Prisma Migrate)を活用することで、スキーマ変更をバージョン管理し、複数環境間での整合性を維持できます。チームでの開発や、CI/CDパイプラインへの組み込みを見据えて、早い段階からマイグレーションツールを導入することをお勧めします。
最後に、本番環境でのスキーマ変更は慎重に行う必要があります。十分なテスト、バックアップの取得、ロールバック手順の準備を怠らず、安全なスキーマ変更を実践してください。
参考リンク#