第4章 CRUD と ACID — データを安全に出し入れする仕組み
この章で学ぶこと
- CRUD 4 操作 (Create / Read / Update / Delete) を SQL の動詞と対応付けて説明できる
- トランザクションが必要な場面を判断し、
BEGINCOMMITROLLBACKの役割を説明できる - ACID 4 特性 (Atomicity / Consistency / Isolation / Durability) を自分の言葉で説明できる
- データ整合性を守るための制約 (NOT NULL / CHECK / 外部キー) を SQL で書ける
第 1 章 データベースの基本概念 を理解していること。SQL の経験はなくて構いません。本章で出てくる SQL 文を初めて見る場合でも、何を意図しているかは文中で説明します。
SQL の例で使うデータベース
本章 (および本ガイド全体) のコード例は、特に断りがない限り PostgreSQL の方言を採用します。MySQL など他の RDBMS でも基本的には動きますが、自動採番 (SERIAL) や一部の制約構文に差があります。他の方言については章末の補足を参照してください。
Stack Overflow Developer Survey 2025 で PostgreSQL は専門開発者の 58.2% が使用する最も使われているデータベースで、新規プロジェクトでの採用も増えています (Bytebase PostgreSQL vs MySQL 2026 参照)。本ガイドの読者が現代の Web 開発現場で出会いやすい方言を優先しています。
このPostgreSQLをAWS上のマネージドサービスとして運用する際の設計 (インスタンスサイズ・可用性・認証情報の管理) は、AWS実践ガイド 第9章: RDS で扱っています。
CRUD — データベースの 4 つの基本操作
データベースに対する操作はすべて、次の 4 つに集約されます。
それぞれが SQL の動詞と 1 対 1 で対応します。
| CRUD | SQL の動詞 | 用途 |
|---|---|---|
| Create | INSERT | 新規データの追加 |
| Read | SELECT | データの検索・取得 |
| Update | UPDATE | 既存データの変更 |
| Delete | DELETE | データの削除 |
SNS での CRUD 操作の例
イメージしやすくするため、SNS の投稿機能を例に各操作のタイミングを見てみます。
SQL の例
-- Create: 新規投稿の追加
INSERT INTO posts (user_id, body, created_at)
VALUES (1, 'はじめての投稿です', NOW());
-- Read: ユーザー 1 の最新 10 件
SELECT id, body, created_at
FROM posts
WHERE user_id = 1
ORDER BY created_at DESC
LIMIT 10;
-- Update: 投稿の本文を編集
UPDATE posts
SET body = '投稿を編集しました', updated_at = NOW()
WHERE id = 42;
-- Delete: 投稿を削除
DELETE FROM posts
WHERE id = 42;
データの一貫性 (Consistency) を守る
データの一貫性 (consistency) とは、データベース内のデータが矛盾なく揃っている状態のことです。
たとえば商品マスタの商品名を「ノートパソコン」から「ノート PC Pro」に変更したのに、注文テーブルには古い商品名「ノートパソコン」が残ったままになっていたら、レポートを集計するたびに混乱が起きます。
| 注文 ID | 顧客名 | 商品コード | 商品名 (注文時) | 注文日 |
|---|---|---|---|---|
| 1 | 山田 太郎 | P001 | ノート PC Pro | 2026-01-01 |
| 2 | 鈴木 花子 | P001 | ノートパソコン | 2026-01-02 |
| 3 | 佐藤 次郎 | P001 | ノート PC Pro | 2026-01-03 |
同じ商品コード P001 なのに商品名が違うのは明らかにおかしい状態です。
このような矛盾を防ぐには、「同じ情報を 1 箇所にしか持たない」設計 (正規化) と、「不正なデータの登録自体を弾く仕組み」 (制約) の 2 つを組み合わせます。
詳しい正規化の手順は第 11 章「正規化」で扱います。本章では「制約」のほうを見ていきます。
データの妥当性検証 — 制約 (Constraint)
データベースは、不正なデータの登録を防ぐ機能 (制約) を持っています。アプリケーション側でもバリデーションは行いますが、データベース側でも最後の砦として制約をかけるのが安全な設計です。
よく使う制約
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL, -- 空欄禁止
price NUMERIC(10, 2) NOT NULL CHECK (price > 0), -- 0 円以下は禁止
stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
category VARCHAR(50) NOT NULL CHECK (category IN ('food', 'appliance', 'clothing')),
email_contact VARCHAR(255) CHECK (email_contact LIKE '%@%') -- メール形式の簡易チェック
);
| 制約 | 役割 |
|---|---|
NOT NULL | カラムを空にできない (必須項目) |
CHECK (条件) | カラムの値が条件を満たすことを保証する |
UNIQUE | カラムの値が重複しないことを保証する |
PRIMARY KEY | レコードを一意に識別する (NOT NULL + UNIQUE) |
FOREIGN KEY | 別のテーブルのレコードを参照する |
DEFAULT 値 | 値を省略した場合に使われる初期値 |
上の例で email_contact LIKE '%@%' としているのは、あくまで「単純な誤入力を弾く」程度のチェックです。RFC 準拠の厳密なメールアドレス検証は正規表現が複雑になりすぎるため、アプリケーション側で行うのが一般的です。データベース側の CHECK は「明らかにおかしい値」を防ぐ最後の砦と考えてください。
トランザクション — 「全て成功か、全て取り消し」を保証する
ここまでは 1 つの操作に注目してきました。しかし実際のアプリでは、複数の操作をひとまとまりとして扱いたい場面が頻繁にあります。
代表例は銀行振込です。「A さんの口座から 10,000 円を引く」「B さんの口座に 10,000 円を足す」という 2 つの UPDATE が必要ですが、片方だけ成功して片方が失敗したら、お金が消滅するか湧き出るかします。
トランザクションとは、複数の操作をまとめて 1 つの「単位」として扱う仕組みです。
BEGIN(またはSTART TRANSACTION): トランザクションの開始COMMIT: ここまでの変更を確定 (永続化)ROLLBACK: ここまでの変更を破棄して開始前の状態に戻す
SQL の例
BEGIN;
UPDATE accounts SET balance = balance - 10000 WHERE id = 'A';
UPDATE accounts SET balance = balance + 10000 WHERE id = 'B';
-- 残高がマイナスになっていないかチェックなど
COMMIT; -- 両方成功したら確定
-- もしエラーがあれば代わりに ROLLBACK;
ACID 特性 — トランザクションが守る 4 つの約束
トランザクションが信頼できる仕組みとして機能するために、データベースは次の 4 つの性質を保証します。それぞれの頭文字をとって ACID 特性 と呼びます。
| 特性 | 意味 | 銀行振込での具体例 |
|---|---|---|
| Atomicity (原子性) | トランザクション内の全操作が全て成功するか、全て失敗するかのどちらか | A から引いた直後にサーバが落ちても、B に足されていなければ A も元に戻る |
| Consistency (一貫性) | 制約を満たした状態でトランザクションが終わる | 「残高は 0 以上」制約があれば、それを破る振込は弾かれる |
| Isolation (独立性) | 並行する別のトランザクションから途中状態が見えない | 振込の途中で別の人が残高を読んでも、引いた直後の半端な状態は見えない |
| Durability (永続性) | COMMIT した変更は障害があっても失われない | 振込完了後にサーバが落ちても、振込履歴と残高は維持される |
「一貫性」という言葉は、データの矛盾がない状態 (本章前半の説明) と、ACID の C (制約を満たしたまま) という 2 つの意味で使われます。実際の意味はほぼ同じで、「データベースがおかしな状態にならない」ことを別の角度から表現しているにすぎません。
障害対策とバックアップ
データベースは、ハードウェア故障・ソフトウェアバグ・人為ミス・災害といった障害からデータを守る仕組みも持っています。本ガイドでは概念だけ紹介し、本格的な運用 (バックアップポリシー設計・レプリケーション運用など) は範囲外とします。
| 仕組み | ざっくり何をするか |
|---|---|
| バックアップ | データを丸ごとコピーして別の場所に保管 |
| レプリケーション | 別サーバに変更をリアルタイム複製 |
| トランザクションログ | 確定した操作を順番に記録、特定時点まで巻き戻せる |
| 冗長化 | 同じ役割のサーバを複数台用意して 1 台壊れても継続 |
本格的なバックアップ運用 (フル / 差分 / 増分の組み合わせ・3-2-1 ルール・PITR など) や、レプリケーションの構成 (同期 / 非同期 / リーダーフォロワー) は、運用設計の話になるため本ガイドの範囲外です。クラウドサービス (AWS RDS / Google Cloud SQL など) のマネージドデータベースを使う場合は、これらの多くを自動でやってくれます。
演習
演習 1: ACID クイズ
ACID の D は何の頭文字でしょうか? また、それは何を保証する性質ですか?
解答例
D = Durability (永続性)。COMMIT で確定した変更は、その後に障害 (サーバ停止・電源断など) が起きても失われないことを保証します。
仕組みとしては、COMMIT の時点で変更内容をディスク上の安全な領域 (トランザクションログ) に書き込んでから「確定」と返事をするのが一般的です。これにより、メモリ上のデータが消えても、ログから復元できます。
ありがちな間違い: 「Durability = Distributed」と混同すること。Distributed (分散) は ACID ではなく、別のキーワード (CAP 定理など) の話題です。
次のうち、トランザクションが必要な場面はどれでしょうか? 当てはまるものを全て選んでください。
- ブログサイトで記事を 1 件取得して表示する
- ネット通販で注文を確定し、在庫を減らし、決済情報を記録する
- SNS の「いいね」ボタンを押して、投稿の「いいね数」と、誰がいいねしたかの 2 つを記録する
- 検索ページでキーワード検索の結果を取得する
解答例
2 と 3 が該当します。
- 2 (注文確定): 「注文を作る・在庫を減らす・決済情報を記録する」の 3 つを「全て成功」させたい。途中で失敗したら全て取り消したい (例: 決済に失敗したのに在庫だけ減ったら売り損ねる)。
- 3 (いいね): 「いいね数を +1 する」「誰がいいねしたかの記録を追加する」の 2 つを揃えたい。片方だけ成功するとカウントとリストが矛盾する。
- 1 (記事取得): 読み取りだけなのでトランザクションの「全部成功 or 全部取り消し」の意味は薄い。
- 4 (検索): 同じく読み取りのみ。
補足: 1 や 4 のような「読み取りだけ」でも、複数のクエリで同じスナップショットを見たい場合 (例: 集計の途中で他の人が更新しても集計結果が一貫しているように) はトランザクションを使うことがあります。これは独立性 (Isolation) の話題で、本ガイドでは深入りしません。
演習 2: 制約を追加する
次の users テーブル定義に対し、3 つの制約を追加してください。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
age INTEGER,
role VARCHAR(20)
);
要件:
- メールアドレスは必須かつ重複不可
- 年齢は 0 以上 150 以下
- ロールは 'admin' / 'editor' / 'viewer' のいずれかで、初期値は 'viewer'
解答例
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
age INTEGER CHECK (age >= 0 AND age <= 150),
role VARCHAR(20) NOT NULL DEFAULT 'viewer'
CHECK (role IN ('admin', 'editor', 'viewer'))
);
なぜそうなるか:
NOT NULL UNIQUE: 「必須」(NOT NULL) と「重複不可」(UNIQUE) を組み合わせるCHECK (age >= 0 AND age <= 150): 範囲を 2 つの条件で挟むCHECK (role IN ('admin', 'editor', 'viewer')): 列挙された値だけを許可DEFAULT 'viewer': 値を省略したときの初期値
別解: PostgreSQL では、列挙型 ENUM を別途定義して使う書き方もあります。
CREATE TYPE user_role AS ENUM ('admin', 'editor', 'viewer');
CREATE TABLE users (
-- ...
role user_role NOT NULL DEFAULT 'viewer'
);
ENUM は値を後から追加するときに ALTER TYPE が必要で柔軟性が下がる反面、ロール一覧が型として明示されて保守しやすくなります。どちらを使うかはチームの好みです。
ありがちな間違い: email VARCHAR(255) NOT NULL だけ書いて UNIQUE を忘れること。同じメールアドレスで複数アカウントが作れてしまい、ログイン処理で困る場面が出ます。
補足: 他の RDBMS での書き方
本章では PostgreSQL の方言を使いましたが、他の主要な RDBMS では一部の構文が違います。
| 構文 | PostgreSQL | MySQL | SQL Server |
|---|---|---|---|
| 自動採番の主キー | SERIAL または GENERATED ALWAYS AS IDENTITY | AUTO_INCREMENT | IDENTITY(1,1) |
| トランザクション開始 | BEGIN または START TRANSACTION | START TRANSACTION または BEGIN | BEGIN TRANSACTION |
| 現在日時 | NOW() または CURRENT_TIMESTAMP | NOW() | GETDATE() |
| 文字列連結 | ` | またはCONCAT()` |
CRUD の基本 (SELECT INSERT UPDATE DELETE) や制約 (NOT NULL CHECK UNIQUE FOREIGN KEY) は、どの RDBMS でもほぼ同じ書き方で動きます。
まとめ
この章で学んだことを整理します。
- CRUD は
INSERT/SELECT/UPDATE/DELETEの 4 動詞に対応する - 制約 (
NOT NULL/CHECK/UNIQUE/FOREIGN KEY) で不正なデータの登録自体を弾ける - トランザクションは複数の操作を「全て成功 or 全て取り消し」のひとまとまりとして扱う
- ACID 4 特性 (原子性・一貫性・独立性・永続性) がトランザクションの信頼性を支える
次章では、なぜデータを表 (テーブル) の形で扱うのか — リレーショナルデータベースの考え方を扱います。