メインコンテンツまでスキップ

第4章 CRUD と ACID — データを安全に出し入れする仕組み

この章で学ぶこと

  • CRUD 4 操作 (Create / Read / Update / Delete) を SQL の動詞と対応付けて説明できる
  • トランザクションが必要な場面を判断し、BEGIN COMMIT ROLLBACK の役割を説明できる
  • ACID 4 特性 (Atomicity / Consistency / Isolation / Durability) を自分の言葉で説明できる
  • データ整合性を守るための制約 (NOT NULL / CHECK / 外部キー) を SQL で書ける
前提知識

第 1 章 データベースの基本概念 を理解していること。SQL の経験はなくて構いません。本章で出てくる SQL 文を初めて見る場合でも、何を意図しているかは文中で説明します。

SQL の例で使うデータベース

本章 (および本ガイド全体) のコード例は、特に断りがない限り PostgreSQL の方言を採用します。MySQL など他の RDBMS でも基本的には動きますが、自動採番 (SERIAL) や一部の制約構文に差があります。他の方言については章末の補足を参照してください。

なぜ PostgreSQL 中心か

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 で対応します。

CRUDSQL の動詞用途
CreateINSERT新規データの追加
ReadSELECTデータの検索・取得
UpdateUPDATE既存データの変更
DeleteDELETEデータの削除

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 Pro2026-01-01
2鈴木 花子P001ノートパソコン2026-01-02
3佐藤 次郎P001ノート PC Pro2026-01-03

同じ商品コード P001 なのに商品名が違うのは明らかにおかしい状態です。

このような矛盾を防ぐには、「同じ情報を 1 箇所にしか持たない」設計 (正規化) と、「不正なデータの登録自体を弾く仕組み」 (制約) の 2 つを組み合わせます。

詳しい正規化の手順は第 11 章「正規化」で扱います。本章では「制約」のほうを見ていきます。

データの妥当性検証 — 制約 (Constraint)

データベースは、不正なデータの登録を防ぐ機能 (制約) を持っています。アプリケーション側でもバリデーションは行いますが、データベース側でも最後の砦として制約をかけるのが安全な設計です。

よく使う制約

products テーブルの定義例 (PostgreSQL)
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 値値を省略した場合に使われる初期値
メール形式の CHECK は最小限

上の例で email_contact LIKE '%@%' としているのは、あくまで「単純な誤入力を弾く」程度のチェックです。RFC 準拠の厳密なメールアドレス検証は正規表現が複雑になりすぎるため、アプリケーション側で行うのが一般的です。データベース側の CHECK は「明らかにおかしい値」を防ぐ最後の砦と考えてください。

トランザクション — 「全て成功か、全て取り消し」を保証する

ここまでは 1 つの操作に注目してきました。しかし実際のアプリでは、複数の操作をひとまとまりとして扱いたい場面が頻繁にあります。

代表例は銀行振込です。「A さんの口座から 10,000 円を引く」「B さんの口座に 10,000 円を足す」という 2 つの UPDATE が必要ですが、片方だけ成功して片方が失敗したら、お金が消滅するか湧き出るかします。

トランザクションとは、複数の操作をまとめて 1 つの「単位」として扱う仕組みです。

  • BEGIN (または START TRANSACTION): トランザクションの開始
  • COMMIT: ここまでの変更を確定 (永続化)
  • ROLLBACK: ここまでの変更を破棄して開始前の状態に戻す

SQL の例

銀行振込のトランザクション (PostgreSQL)
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 した変更は障害があっても失われない振込完了後にサーバが落ちても、振込履歴と残高は維持される
なぜ Consistency が 2 回出てくるか

「一貫性」という言葉は、データの矛盾がない状態 (本章前半の説明) と、ACID の C (制約を満たしたまま) という 2 つの意味で使われます。実際の意味はほぼ同じで、「データベースがおかしな状態にならない」ことを別の角度から表現しているにすぎません。

障害対策とバックアップ

データベースは、ハードウェア故障・ソフトウェアバグ・人為ミス・災害といった障害からデータを守る仕組みも持っています。本ガイドでは概念だけ紹介し、本格的な運用 (バックアップポリシー設計・レプリケーション運用など) は範囲外とします。

仕組みざっくり何をするか
バックアップデータを丸ごとコピーして別の場所に保管
レプリケーション別サーバに変更をリアルタイム複製
トランザクションログ確定した操作を順番に記録、特定時点まで巻き戻せる
冗長化同じ役割のサーバを複数台用意して 1 台壊れても継続
本ガイドの範囲外

本格的なバックアップ運用 (フル / 差分 / 増分の組み合わせ・3-2-1 ルール・PITR など) や、レプリケーションの構成 (同期 / 非同期 / リーダーフォロワー) は、運用設計の話になるため本ガイドの範囲外です。クラウドサービス (AWS RDS / Google Cloud SQL など) のマネージドデータベースを使う場合は、これらの多くを自動でやってくれます。

演習

演習 1: ACID クイズ

演習 1-1

ACID の D は何の頭文字でしょうか? また、それは何を保証する性質ですか?

解答例

D = Durability (永続性)COMMIT で確定した変更は、その後に障害 (サーバ停止・電源断など) が起きても失われないことを保証します。

仕組みとしては、COMMIT の時点で変更内容をディスク上の安全な領域 (トランザクションログ) に書き込んでから「確定」と返事をするのが一般的です。これにより、メモリ上のデータが消えても、ログから復元できます。

ありがちな間違い: 「Durability = Distributed」と混同すること。Distributed (分散) は ACID ではなく、別のキーワード (CAP 定理など) の話題です。

演習 1-2

次のうち、トランザクションが必要な場面はどれでしょうか? 当てはまるものを全て選んでください。

  1. ブログサイトで記事を 1 件取得して表示する
  2. ネット通販で注文を確定し、在庫を減らし、決済情報を記録する
  3. SNS の「いいね」ボタンを押して、投稿の「いいね数」と、誰がいいねしたかの 2 つを記録する
  4. 検索ページでキーワード検索の結果を取得する
解答例

2 と 3 が該当します

  • 2 (注文確定): 「注文を作る・在庫を減らす・決済情報を記録する」の 3 つを「全て成功」させたい。途中で失敗したら全て取り消したい (例: 決済に失敗したのに在庫だけ減ったら売り損ねる)。
  • 3 (いいね): 「いいね数を +1 する」「誰がいいねしたかの記録を追加する」の 2 つを揃えたい。片方だけ成功するとカウントとリストが矛盾する。
  • 1 (記事取得): 読み取りだけなのでトランザクションの「全部成功 or 全部取り消し」の意味は薄い。
  • 4 (検索): 同じく読み取りのみ。

補足: 1 や 4 のような「読み取りだけ」でも、複数のクエリで同じスナップショットを見たい場合 (例: 集計の途中で他の人が更新しても集計結果が一貫しているように) はトランザクションを使うことがあります。これは独立性 (Isolation) の話題で、本ガイドでは深入りしません。

演習 2: 制約を追加する

演習 2

次の users テーブル定義に対し、3 つの制約を追加してください。

CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
age INTEGER,
role VARCHAR(20)
);

要件:

  • メールアドレスは必須かつ重複不可
  • 年齢は 0 以上 150 以下
  • ロールは 'admin' / 'editor' / 'viewer' のいずれかで、初期値は 'viewer'
解答例
users テーブル (制約追加版)
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 では一部の構文が違います。

構文PostgreSQLMySQLSQL Server
自動採番の主キーSERIAL または GENERATED ALWAYS AS IDENTITYAUTO_INCREMENTIDENTITY(1,1)
トランザクション開始BEGIN または START TRANSACTIONSTART TRANSACTION または BEGINBEGIN TRANSACTION
現在日時NOW() または CURRENT_TIMESTAMPNOW()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 特性 (原子性・一貫性・独立性・永続性) がトランザクションの信頼性を支える

次章では、なぜデータを表 (テーブル) の形で扱うのか — リレーショナルデータベースの考え方を扱います。