読み込み中...
読み込み中...
読み込み中...
読み込み中...
読み込み中...
私たちが日常的に使うサービスの裏側では、膨大なデータが整理・管理されています。たとえば、ネットショッピングで商品を検索したり、注文履歴を確認できるのは、データベースが情報を体系的に管理しているからです。
このセクションでは、データベースとは何かという基本概念から始め、現在最も広く使われている 関係データベース (リレーショナルデータベース)の仕組み、データの関係性を図で表す ER図 、そしてデータの重複や矛盾を防ぐ 正規化 の手法を学びます。
データベース(DB)とは、複数の利用者やアプリケーションが 共有して利用 できるように、データを整理・蓄積したものです。ノートに手書きでメモするのではなく、みんなが同じ本棚から本を探せるように整理された図書館をイメージしてください。
データベースを管理するソフトウェアを DBMS (Database Management System: データベース管理システム)と呼びます。
| 役割 | 説明 |
|---|---|
| データの一元管理 | データを1箇所で管理し、重複や矛盾を防ぐ |
| 同時アクセス制御 | 複数ユーザーが同時にアクセスしてもデータが壊れないようにする |
| 障害回復 | システム障害が発生してもデータを復旧できる |
| セキュリティ管理 | アクセス権限を設定し、不正なアクセスを防ぐ |
| データの独立性 | アプリケーションとデータ構造を分離し、片方の変更が他方に影響しにくくする |
| 種類 | 特徴 | 例 |
|---|---|---|
| 関係データベース(RDB) | データを 表(テーブル) で管理する。最も広く使われている | PostgreSQL, MySQL, Oracle |
| 階層型データベース | データを木構造(親子関係)で管理する | IMS |
| ネットワーク型データベース | データを網目状の関係で管理する | IDMS |
| NoSQL データベース | RDB以外の新しいタイプの総称。大量データや高速処理に強い | MongoDB, Redis, DynamoDB |
FE試験では 関係データベース が圧倒的に重要です。以降は関係データベースを中心に解説します。
関係データベースでは、データを 表(テーブル) の形式で管理します。表計算ソフト(Excel)のシートに似ていますが、厳密なルールがあります。
社員テーブルの例:
| 社員番号 | 氏名 | 部署コード | 入社年 |
|---|---|---|---|
| E001 | 田中太郎 | D01 | 2020 |
| E002 | 鈴木花子 | D02 | 2021 |
| E003 | 佐藤次郎 | D01 | 2022 |
| 用語 | 別名 | 意味 |
|---|---|---|
| 表(テーブル) | リレーション | データを格納する2次元の表 |
| 行(レコード) | タプル、組 | 表の1行分のデータ(1人分の社員情報) |
| 列(カラム) | 属性、フィールド | 表の1列分の項目(社員番号、氏名など) |
| 主キー(Primary Key) | — | 各行を 一意に識別 できる列。重複・NULL不可 |
| 外部キー(Foreign Key) | — | 他のテーブルの主キーを参照する列。テーブル間の関連を表す |
上の例では「社員番号」が主キーです。「部署コード」が別の「部署テーブル」の主キーを参照していれば、それは外部キーです。
データベースの構造定義を スキーマ と呼びます。ANSI/SPARC では3つの層に分けて定義します。
| スキーマ | 別名 | 役割 |
|---|---|---|
| 外部スキーマ | ビュー | 各ユーザーから見えるデータの範囲・形式 |
| 概念スキーマ | 論理スキーマ | データベース全体の論理的な構造定義 |
| 内部スキーマ | 物理スキーマ | データの物理的な格納方法(インデックス、ファイル構成など) |
3層に分けることで、ある層の変更が他の層に影響しにくくなります。これを データの独立性 と呼びます。たとえば、内部スキーマ(保存方法)を変更しても、概念スキーマ(論理構造)が変わらなければアプリケーションへの影響はありません。
ER図は、データベースで管理する情報の 実体(エンティティ) と 関連(リレーションシップ) を図で表したものです。データベースを設計する際に、テーブル構造を視覚的に整理するために使います。
エンティティ間の関連が「1対1」「1対多」「多対多」のいずれかを示します。
| 多重度 | 意味 | 例 |
|---|---|---|
| 1対1 | 一方の1件に対して他方も1件 | 社員と社員証 |
| 1対多 | 一方の1件に対して他方が複数件 | 部署と社員(1つの部署に複数の社員) |
| 多対多 | 双方が複数件で関連 | 学生と講義(1人の学生が複数の講義を受講) |
多対多の関連は、中間テーブル(連関エンティティ)を作って1対多に分解するのが一般的です。
上の ER 図は「1つの部署に複数の社員が所属する(1対多)」と「学生と講義が多対多の関係」を示しています。
正規化とは、テーブルのデータの 重複や矛盾を排除 し、効率的で整合性の高い構造に整理する手法です。
身近な例で考えてみましょう。もし注文情報を1つのテーブルにすべて詰め込むと、同じ顧客の住所が何行にも繰り返し登場します。住所が変わったとき全行を更新しなければならず、1行でも漏れると矛盾が生じます。正規化はこの問題を解決します。
| 問題 | 説明 |
|---|---|
| 挿入異常 | 不要なデータがないと新しいデータを登録できない |
| 更新異常 | 同じデータが複数箇所にあり、一部だけ更新すると矛盾が生じる |
| 削除異常 | あるデータを削除すると、関連する別のデータまで失われる |
ルール: 繰り返し項目をなくし、すべてのセルに1つの値だけが入るようにする
| 注文番号 | 顧客名 | 商品名 | 数量 |
|---|---|---|---|
| 1001 | 田中 | ノート, ペン | 3, 2 |
| 1002 | 鈴木 | 消しゴム | 1 |
注文番号 1001 の「商品名」と「数量」に複数の値が入っています。これは 繰り返し項目 です。
繰り返し項目を別の行に展開します。
| 注文番号 | 顧客名 | 商品名 | 数量 |
|---|---|---|---|
| 1001 | 田中 | ノート | 3 |
| 1001 | 田中 | ペン | 2 |
| 1002 | 鈴木 | 消しゴム | 1 |
これで各セルに値が1つだけ入るようになりました。ただし「田中」が2行に重複しています。この重複を解消するのが次の正規化です。
ルール: 部分関数従属を排除する(主キーの一部だけに依存する列を別テーブルに分離する)
第1正規形のテーブルでは、主キーは「注文番号 + 商品名」の複合キーです。しかし「顧客名」は「注文番号」だけで決まり、「商品名」には依存しません。これが 部分関数従属 です。
部分関数従属している列を別テーブルに分離します。
注文テーブル:
| 注文番号(PK) | 顧客名 |
|---|---|
| 1001 | 田中 |
| 1002 | 鈴木 |
注文明細テーブル:
| 注文番号(PK, FK) | 商品名(PK) | 数量 |
|---|---|---|
| 1001 | ノート | 3 |
| 1001 | ペン | 2 |
| 1002 | 消しゴム | 1 |
これで「顧客名」の重複がなくなりました。
ルール: 推移的関数従属を排除する(主キー以外の列に依存する列を別テーブルに分離する)
注文テーブルに「顧客コード」と「顧客名」「顧客住所」があるとします。
| 注文番号(PK) | 顧客コード | 顧客名 | 顧客住所 |
|---|---|---|---|
| 1001 | C01 | 田中 | 東京都 |
| 1002 | C02 | 鈴木 | 大阪府 |
| 1003 | C01 | 田中 | 東京都 |
「顧客名」と「顧客住所」は主キー「注文番号」に直接依存しているのではなく、「注文番号 → 顧客コード → 顧客名・顧客住所」と間接的に依存しています。これが 推移的関数従属 です。
推移的関数従属している列を別テーブルに分離します。
注文テーブル:
| 注文番号(PK) | 顧客コード(FK) |
|---|---|
| 1001 | C01 |
| 1002 | C02 |
| 1003 | C01 |
顧客テーブル:
| 顧客コード(PK) | 顧客名 | 顧客住所 |
|---|---|---|
| C01 | 田中 | 東京都 |
| C02 | 鈴木 | 大阪府 |
これで顧客情報の重複が完全に排除されました。住所変更時も顧客テーブルの1行を更新するだけで済みます。
| 正規形 | 排除する問題 | 操作 |
|---|---|---|
| 第1正規形 | 繰り返し項目 | 各セルに1つの値だけにする |
| 第2正規形 | 部分関数従属 | 主キーの一部にしか依存しない列を分離 |
| 第3正規形 | 推移的関数従属 | 主キー以外の列に依存する列を分離 |
正規化を進めるとデータの冗長性は減りますが、テーブルが増えるため結合(JOIN)が必要になり、検索性能が低下する場合があります。実務では意図的に正規化を崩す 非正規化(デノーマライゼーション) を行うこともあります。FE試験では第3正規形までが出題範囲です。
ポイント
関係データベースはデータを 表(テーブル) で管理し、 主キー で各行を一意に識別する。 外部キー でテーブル間を関連付ける。ER図は エンティティ (実体)と リレーションシップ (関連)でデータ構造を可視化する。多対多の関連は中間テーブルで1対多に分解。正規化は 第1正規形 (繰り返し排除)→ 第2正規形 (部分関数従属排除)→ 第3正規形 (推移的関数従属排除)の順で進める。3層スキーマ(外部・概念・内部)はデータの独立性を確保する仕組み。
用語
SQL(Structured Query Language)は、関係データベースを操作するための標準的な言語です。プログラミング言語のように複雑なロジックを書くのではなく、「何をしたいか」を宣言的に記述するのが特徴です。
日常の例えで言えば、SQL は「図書館の司書さんへの依頼」のようなものです。「推理小説の棚から、著者が東野圭吾の本を、出版年が新しい順に5冊取ってきてください」のように、欲しいデータの条件を伝えると、DBMS が最適な方法で取得してくれます。
このセクションでは、SQL の3つの分類と、SELECT 文を中心とした実践的なクエリの書き方を学びます。
| 分類 | 正式名称 | 役割 | 主なコマンド |
|---|---|---|---|
| DDL | Data Definition Language(データ定義言語) | テーブルやインデックスの作成・変更・削除 | CREATE, ALTER, DROP |
| DML | Data Manipulation Language(データ操作言語) | データの検索・挿入・更新・削除 | SELECT, INSERT, UPDATE, DELETE |
| DCL | Data Control Language(データ制御言語) | アクセス権限の付与・取消 | GRANT, REVOKE |
FE試験では DML (特に SELECT)が最も多く出題されます。DDL と DCL はコマンド名と役割を覚えておきましょう。
テーブルの作成・変更・削除を行うコマンドです。
CREATE TABLE employees (
emp_id CHAR(4) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
dept_code CHAR(3),
salary INTEGER DEFAULT 0,
FOREIGN KEY (dept_code) REFERENCES departments(dept_code)
);
| 制約 | 意味 |
|---|---|
| PRIMARY KEY | 主キー。一意かつ NULL 不可 |
| NOT NULL | NULL(空)を許可しない |
| UNIQUE | 値の重複を許可しない(NULL は許可) |
| DEFAULT | 値を省略した場合のデフォルト値 |
| FOREIGN KEY | 外部キー。参照先テーブルに存在する値のみ許可 |
| CHECK | 値の範囲や条件を制約する |
-- 列の追加
ALTER TABLE employees ADD phone VARCHAR(20);
-- 列の削除
ALTER TABLE employees DROP COLUMN phone;
DROP TABLE employees;
ビューは 仮想的なテーブル です。実データを持たず、SELECT 文の結果を表のように扱えます。
CREATE VIEW high_salary_employees AS
SELECT emp_id, name, salary
FROM employees
WHERE salary >= 500000;
ビューを使うことで、複雑なクエリを簡略化したり、ユーザーに見せる列を制限してセキュリティを確保できます。これは外部スキーマに対応します。
権限を管理するコマンドです。
-- user01 に employees テーブルの SELECT 権限を付与
GRANT SELECT ON employees TO user01;
-- user01 から SELECT 権限を取消
REVOKE SELECT ON employees FROM user01;
GRANT は権限を与え、 REVOKE は権限を取り消します。データベースの安全性(RASIS の S)に直結する重要な機能です。
以降の SQL 例では、以下の3つのテーブルを使います。
departments(部署テーブル):
| dept_code | dept_name |
|---|---|
| D01 | 営業部 |
| D02 | 開発部 |
| D03 | 人事部 |
employees(社員テーブル):
| emp_id | name | dept_code | salary |
|---|---|---|---|
| E001 | 田中太郎 | D01 | 350000 |
| E002 | 鈴木花子 | D02 | 420000 |
| E003 | 佐藤次郎 | D01 | 380000 |
| E004 | 山田美咲 | D02 | 450000 |
| E005 | 高橋健一 | NULL | 300000 |
orders(注文テーブル):
| order_id | emp_id | product | amount |
|---|---|---|---|
| 1 | E001 | ノートPC | 150000 |
| 2 | E001 | モニタ | 45000 |
| 3 | E003 | キーボード | 8000 |
| 4 | E002 | ノートPC | 150000 |
SELECT 文はデータを検索するための最も重要なコマンドです。
SELECT 列名1, 列名2, ...
FROM テーブル名
WHERE 条件
ORDER BY 列名 [ASC | DESC];
SELECT * FROM employees;
* は「すべての列」を意味します。
SELECT name, salary FROM employees;
| name | salary |
|---|---|
| 田中太郎 | 350000 |
| 鈴木花子 | 420000 |
| 佐藤次郎 | 380000 |
| 山田美咲 | 450000 |
| 高橋健一 | 300000 |
-- 給与が40万以上の社員
SELECT name, salary FROM employees
WHERE salary >= 400000;
| name | salary |
|---|---|
| 鈴木花子 | 420000 |
| 山田美咲 | 450000 |
| 演算子 | 意味 | 例 |
|---|---|---|
= | 等しい | dept_code = 'D01' |
<> または != | 等しくない | dept_code <> 'D01' |
<, >, <=, >= | 大小比較 | salary >= 400000 |
BETWEEN a AND b | a以上b以下 | salary BETWEEN 300000 AND 400000 |
IN (値1, 値2, ...) | いずれかに一致 | dept_code IN ('D01', 'D02') |
LIKE | パターンマッチ | name LIKE '田%'(田で始まる) |
IS NULL | NULLである | dept_code IS NULL |
AND, OR, NOT | 論理演算 | salary >= 400000 AND dept_code = 'D02' |
LIKE のワイルドカード:
% : 0文字以上の任意の文字列_ : 任意の1文字-- 名前に「田」を含む社員
SELECT name FROM employees WHERE name LIKE '%田%';
-- 給与の高い順(降順)
SELECT name, salary FROM employees
ORDER BY salary DESC;
複数列での並べ替えも可能です。
-- 部署コード昇順 → 同じ部署内では給与降順
SELECT name, dept_code, salary FROM employees
ORDER BY dept_code ASC, salary DESC;
重複する行を除外します。
-- 社員が所属している部署コードの一覧(重複なし)
SELECT DISTINCT dept_code FROM employees;
| dept_code |
|---|
| D01 |
| D02 |
| NULL |
複数の行をまとめて1つの値を計算する関数です。
| 関数 | 意味 | NULL の扱い |
|---|---|---|
| COUNT(*) | 行数を数える | NULL を含む |
| COUNT(列名) | 指定列の NULL でない値の数 | NULL を除外 |
| SUM(列名) | 合計 | NULL を除外 |
| AVG(列名) | 平均 | NULL を除外 |
| MAX(列名) | 最大値 | NULL を除外 |
| MIN(列名) | 最小値 | NULL を除外 |
-- 社員数と平均給与
SELECT COUNT(*) AS 社員数, AVG(salary) AS 平均給与
FROM employees;
| 社員数 | 平均給与 |
|---|---|
| 5 | 380000 |
注意: COUNT(*) は NULL を含む全行を数えますが、 COUNT(dept_code) は dept_code が NULL の行を除外します。この違いは FE 試験で頻出です。
指定した列の値が同じ行をグループにまとめ、グループごとに集約関数を適用します。
-- 部署ごとの社員数と平均給与
SELECT dept_code, COUNT(*) AS 人数, AVG(salary) AS 平均給与
FROM employees
GROUP BY dept_code;
| dept_code | 人数 | 平均給与 |
|---|---|---|
| D01 | 2 | 365000 |
| D02 | 2 | 435000 |
| NULL | 1 | 300000 |
重要なルール: GROUP BY を使うとき、SELECT に書ける列は「GROUP BY で指定した列」または「集約関数」だけです。それ以外の列を書くとエラーになります。
WHERE 句は グループ化前 の行を絞り込みますが、HAVING 句は グループ化後 のグループを絞り込みます。
-- 平均給与が40万以上の部署
SELECT dept_code, AVG(salary) AS 平均給与
FROM employees
GROUP BY dept_code
HAVING AVG(salary) >= 400000;
| dept_code | 平均給与 |
|---|---|
| D02 | 435000 |
SQL の各句は書く順序と実行される順序が異なります。実行順序を理解すると、WHERE と HAVING の違いが明確になります。
| 実行順序 | 句 | 役割 |
|---|---|---|
| 1 | FROM | 対象テーブルを決定 |
| 2 | WHERE | 行を絞り込み |
| 3 | GROUP BY | グループ化 |
| 4 | HAVING | グループを絞り込み |
| 5 | SELECT | 取得する列を決定 |
| 6 | ORDER BY | 並べ替え |
WHERE は GROUP BY の 前 に実行されるため、集約関数を使った条件は WHERE には書けません。集約関数の結果で絞り込みたいときは HAVING を使います。
複数のテーブルを関連する列で結びつけて、1つの結果として取得する操作です。正規化で分割したテーブルを元の情報に戻すために不可欠です。
両方のテーブルに 一致するデータがある行だけ を取得します。
SELECT e.name, d.dept_name, e.salary
FROM employees e
INNER JOIN departments d ON e.dept_code = d.dept_code;
| name | dept_name | salary |
|---|---|---|
| 田中太郎 | 営業部 | 350000 |
| 鈴木花子 | 開発部 | 420000 |
| 佐藤次郎 | 営業部 | 380000 |
| 山田美咲 | 開発部 | 450000 |
高橋健一は dept_code が NULL のため、departments と一致する行がなく結果に含まれません。
左側のテーブル(FROM 側)の行は すべて 残し、右側に一致がなければ NULL で埋めます。
SELECT e.name, d.dept_name, e.salary
FROM employees e
LEFT JOIN departments d ON e.dept_code = d.dept_code;
| name | dept_name | salary |
|---|---|---|
| 田中太郎 | 営業部 | 350000 |
| 鈴木花子 | 開発部 | 420000 |
| 佐藤次郎 | 営業部 | 380000 |
| 山田美咲 | 開発部 | 450000 |
| 高橋健一 | NULL | 300000 |
今度は高橋健一も結果に含まれます(dept_name は NULL)。
右側のテーブル(JOIN 側)の行をすべて残します。
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_code = d.dept_code;
| name | dept_name |
|---|---|
| 田中太郎 | 営業部 |
| 佐藤次郎 | 営業部 |
| 鈴木花子 | 開発部 |
| 山田美咲 | 開発部 |
| NULL | 人事部 |
人事部には社員がいないため name が NULL になりますが、人事部自体は結果に含まれます。
SELECT 文の中にさらに SELECT 文を入れ子にする手法です。内側のクエリの結果を外側のクエリの条件として使います。
-- 平均給与より高い給与の社員を取得
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
内側の SELECT AVG(salary) FROM employees が先に実行されて 380000 が返り、外側では salary > 380000 の条件で絞り込まれます。
| name | salary |
|---|---|
| 鈴木花子 | 420000 |
| 山田美咲 | 450000 |
-- 注文を出したことがある社員を取得
SELECT name
FROM employees
WHERE emp_id IN (SELECT DISTINCT emp_id FROM orders);
| name |
|---|
| 田中太郎 |
| 鈴木花子 |
| 佐藤次郎 |
-- 注文を出したことがある社員を取得(EXISTS版)
SELECT name
FROM employees e
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.emp_id = e.emp_id
);
EXISTS は副問い合わせの結果が「1行以上あるか」を判定します。IN と同じ結果が得られますが、大量データでは EXISTS の方が効率的な場合があります。
-- 部署ごとの平均給与を求め、その中で最も高い部署を取得
SELECT dept_code, avg_salary
FROM (
SELECT dept_code, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_code
) AS dept_avg
WHERE avg_salary = (SELECT MAX(avg_salary) FROM (
SELECT AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_code
) AS dept_avg2);
データの追加・更新・削除を行う DML コマンドです。
-- 1行挿入
INSERT INTO employees (emp_id, name, dept_code, salary)
VALUES ('E006', '伊藤一郎', 'D03', 320000);
-- 別テーブルの検索結果を挿入
INSERT INTO high_salary_log (emp_id, name, salary)
SELECT emp_id, name, salary FROM employees WHERE salary >= 400000;
-- 営業部の社員の給与を10%アップ
UPDATE employees
SET salary = salary * 1.1
WHERE dept_code = 'D01';
注意: WHERE 句を省略すると 全行 が更新されます。
-- 給与が30万未満の社員を削除
DELETE FROM employees
WHERE salary < 300000;
注意: WHERE 句を省略すると 全行 が削除されます。
ポイント
SQL は DDL (CREATE, ALTER, DROP)・ DML (SELECT, INSERT, UPDATE, DELETE)・ DCL (GRANT, REVOKE)の3分類。SELECT 文の実行順序は FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。WHERE は 行の絞り込み 、HAVING は グループの絞り込み 。JOIN は INNER JOIN (一致する行のみ)と LEFT/RIGHT JOIN (片方の全行を残す)を区別する。副問い合わせは WHERE 句(単一値や IN/EXISTS)や FROM 句(インラインビュー)で使える。 COUNT(*) は NULL を含むが COUNT(列名) は NULL を除外する。
用語
銀行の ATM で口座 A から口座 B に10万円を振り込む操作を考えてみましょう。この処理は「口座 A から10万円を引く」と「口座 B に10万円を足す」の2つのステップで構成されています。もし1つ目のステップが成功して2つ目が失敗したら、10万円が消えてしまいます。
このような「一連の処理をまとめて、すべて成功するか、すべて取り消すか」を管理する仕組みが トランザクション です。データベースの信頼性を支える最も重要な概念の1つです。
トランザクションとは、データベースに対する 1つの論理的な作業単位 です。複数の操作をまとめて、 すべて成功(コミット) するか、 すべて取り消し(ロールバック) するかのどちらかにします。
-- 銀行振込のトランザクション例
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100000
WHERE account_id = 'A001'; -- 口座Aから引き出し
UPDATE accounts SET balance = balance + 100000
WHERE account_id = 'B001'; -- 口座Bに入金
COMMIT; -- 両方成功 → 確定
もし途中でエラーが発生した場合:
ROLLBACK; -- すべて取り消し → 振込前の状態に戻る
トランザクションが守るべき4つの性質を ACID特性 と呼びます。
| 特性 | 英語 | 意味 | 銀行振込での例 |
|---|---|---|---|
| A | Atomicity(原子性) | 全部成功か全部取消のどちらか。中途半端な状態にならない | 引き出しと入金が両方成功するか、両方取り消される |
| C | Consistency(一貫性) | トランザクション前後でデータの整合性が保たれる | 振込前後で2口座の合計金額が変わらない |
| I | Isolation(独立性) | 同時実行中のトランザクションが互いに干渉しない | 別の人の振込処理が途中結果を見れない |
| D | Durability(耐久性) | コミット後の変更は障害が起きても失われない | 振込完了後にサーバが故障しても入金額は保持される |
FE試験では、各特性の名称と意味を正確に区別できることが求められます。特に 原子性 (すべてかゼロか)と 独立性 (他のトランザクションから見えない)は頻出です。
複数のトランザクションが同じデータに同時にアクセスすると、データの不整合が起きる可能性があります。これを防ぐ仕組みが 排他制御(ロック) です。
| ロック | 別名 | 他のトランザクションの操作 |
|---|---|---|
| 共有ロック(S ロック) | 読み取りロック | 読み取り: 可能、書き込み: 不可 |
| 占有ロック(X ロック) | 書き込みロック | 読み取り: 不可、書き込み: 不可 |
| 共有ロック(S)を要求 | 占有ロック(X)を要求 | |
|---|---|---|
| 共有ロック(S)がかかっている | 許可(同時読み取り可能) | 待ち |
| 占有ロック(X)がかかっている | 待ち | 待ち |
「共有ロック同士は共存できるが、占有ロックは他のすべてのロックと排他的」と覚えましょう。
ロックをかける範囲(粒度)によって、性能と安全性のバランスが変わります。
| 粒度 | 対象 | 同時実行性 | ロック管理の負荷 |
|---|---|---|---|
| 行ロック | テーブルの1行 | 高い | 大きい |
| ページロック | ディスク上の1ページ | 中程度 | 中程度 |
| テーブルロック | テーブル全体 | 低い | 小さい |
粒度が細かい(行ロック)ほど同時に多くのトランザクションが実行できますが、ロックの管理コストが増えます。
2つ以上のトランザクションが、互いに相手が持つロックの解放を待ち続ける状態を デッドロック と呼びます。永遠に処理が進まなくなります。
| 時刻 | トランザクション T1 | トランザクション T2 |
|---|---|---|
| t1 | テーブル A をロック | テーブル B をロック |
| t2 | テーブル B をロック要求 → T2 を待つ | テーブル A をロック要求 → T1 を待つ |
| t3 | (永遠に待ち続ける) | (永遠に待ち続ける) |
T1 は T2 が B を解放するのを待ち、T2 は T1 が A を解放するのを待つ。どちらも永遠に進めません。
| 対処法 | 説明 |
|---|---|
| 検出と解消 | DBMS がデッドロックを検出し、一方のトランザクションを強制的にロールバック |
| タイムアウト | 一定時間ロック待ちが続いたらタイムアウトでロールバック |
| 予防(ロック順序の統一) | すべてのトランザクションが同じ順序でロックを取得する規約を設ける |
多くの DBMS は「検出と解消」方式を採用しており、デッドロックを検出すると コストの小さいトランザクション を犠牲にしてロールバックします。
トランザクションのロック制御に関する重要なプロトコルです。トランザクションを2つのフェーズに分けます。
| フェーズ | 操作 |
|---|---|
| 拡大相(Growing Phase) | ロックの獲得のみ行う。ロックの解放はしない |
| 縮小相(Shrinking Phase) | ロックの解放のみ行う。新たなロックの獲得はしない |
2相ロックを守ることで、トランザクションの 直列可能性 (複数のトランザクションを同時実行しても、ある順序で1つずつ実行した場合と同じ結果になること)が保証されます。ただし、デッドロックを防ぐことはできません。
データベースに障害が発生した場合、トランザクションの ACID 特性(特に原子性と耐久性)を維持するための回復手法が必要です。
DBMS はすべてのデータ変更操作を ログファイル に記録します。ログには「いつ」「どのトランザクションが」「どのデータを」「どの値からどの値に変更したか」が書かれています。このログを使って障害回復を行います。
DBMS は定期的に チェックポイント を設定し、その時点までのメモリ上の変更内容をディスクに書き出します。障害回復時はチェックポイント以降のログだけを処理すればよいため、回復時間が短縮されます。
障害回復には2つの方法があります。
障害発生時に コミットが完了していなかった トランザクションの変更を取り消し、トランザクション開始前の状態に戻す処理です。
障害発生時に コミットが完了していた が、まだディスクに反映されていなかった変更を再実行する処理です。
| 回復方法 | 対象トランザクション | 使うログ | 操作 |
|---|---|---|---|
| ロールバック | コミット未完了 | 更新前の値 | 変更を取り消す |
| ロールフォワード | コミット済み・ディスク未反映 | 更新後の値 | 変更を再実行する |
| 障害の種類 | 説明 | 回復方法 |
|---|---|---|
| トランザクション障害 | プログラムの論理エラーやデッドロックなどで個別のトランザクションが異常終了 | ログによるロールバック |
| システム障害 | 電源断やOS障害などでDBMS全体が停止 | ログによるロールバック+ロールフォワード |
| 媒体障害 | ディスクの物理的な故障でデータが破損 | バックアップからの復旧+ログによるロールフォワード |
媒体障害が最も深刻で、バックアップが必要です。そのため定期的なバックアップと、バックアップ以降のログ保存が重要になります。
データの変更をディスクに書き込む 前に 、必ずログを先に書き込むルールです。「ログ先書き」とも呼ばれます。
もしデータを先に書いてログを書く前に障害が発生すると、ログがないため回復できません。WAL を守ることで、障害が発生しても必ずログから回復できることが保証されます。
ポイント
トランザクションは「すべて成功(COMMIT)か、すべて取消(ROLLBACK)」の単位。 ACID特性 は A(原子性) ・ C(一貫性) ・ I(独立性) ・ D(耐久性) 。排他制御は 共有ロック (読み取り用、S ロック同士は共存可能)と 占有ロック (書き込み用、排他的)で行う。 デッドロック は互いにロック待ちで永遠に進まない状態。回復は ロールバック (コミット未完了のトランザクションを取り消す、更新前の値を使用)と ロールフォワード (コミット済みの変更を再実行、更新後の値を使用)。 WAL はデータより先にログを書くルール。
用語