じゃあ、READ COMMITTEDの分離レベルが何をしてくれるのか見てみよう。名前の通り、トランザクション内で読むものは全部、他のトランザクションによって「コミット」されたものだけってこと。まるで、公式に記録された噂話しか信じないみたいな感じだね。
真面目な話、このレベルは、他のトランザクションが変更したけどまだコミットしてない内容は見えないって保証してくれる。これは「ダーティリード」(Dirty Read)っていう問題を解決してくれる。でも、気をつけてほしいのは、読んだデータが他のトランザクションのコミットによって途中で変わる可能性があること。これが「ノンリピータブルリード」(Non-Repeatable Read)のリスクになるんだ。
PostgreSQLではREAD COMMITTEDがデフォルトの分離レベルになってる。つまり、特に何も設定しなくてもこのモードで動いてるってこと。
READ COMMITTED分離レベルの使い方例
じゃあ、実際にどう動くのか見てみよう。例えば、accountsっていうテーブルがあって、ユーザーとその残高の情報が入ってるとする:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
account_name TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (account_name, balance)
VALUES ('Alice', 1000.00), ('Bob', 500.00);
次のシチュエーションを想像してみて。セッション1とセッション2、二人の主人公がaccountsテーブルを操作してる。こんな感じ:
セッション1:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_name = 'Alice';
-- まだCOMMITもROLLBACKもしてない。
この時点で、アリスのbalanceから一時的に100引かれてるけど、まだ確定してない。
セッション2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE account_name = 'Alice';
結果:セッション2はアリスの残高を1000.00として見る。なぜなら、セッション1のトランザクションはまだ終わってない(COMMITしてない)から。READ COMMITTEDレベルが「ダーティリード」から守ってくれる。
セッション1がトランザクションを完了:
COMMIT;
これでアリスの残高が更新されて、データベースには900.00が記録された。
セッション2がもう一度クエリを実行:
SELECT balance FROM accounts WHERE account_name = 'Alice';
結果:今度はセッション2がアリスの新しい残高900.00を見る。前のクエリの結果1000.00と違うよね。これが「ノンリピータブルリード」問題。
いつREAD COMMITTEDを使う?
READ COMMITTEDの分離レベルは、パフォーマンスと一貫性のバランスが取れてる。でも、特にピッタリなシーンがいくつかある:
シンプルなCRUD操作: ただデータを読んだり更新したりするだけで、複雑な関連がない場合。
レコードの更新: 例えば、テーブルのデータを一括で更新して、すぐに確定した変更を見たいとき。
トランザクション処理: 決済システムみたいに、ユーザーに確定済みのデータだけ見せたい場合。
でも、複雑な分析クエリや大量データを扱うなら、REPEATABLE READみたいな他の分離レベルを検討した方がいいかも。
READ COMMITTED分離レベルのメリット・デメリット
READ COMMITTED分離レベルは、まさにゴールデンミドルって感じ。ダーティなデータから守ってくれる:他のトランザクションが始めたけどまだ終わってない変更は見えない。つまり、「生焼け」な情報を誰も読まないし、すぐにロールバックされるかもしれない内容も見えない。
このモードは、もっと厳しいレベル(REPEATABLE READやSERIALIZABLE)より速い。なぜなら、複雑なロックや追加チェックがいらないから。軽くて、それでいて信頼できる。だからデフォルトで使われてて、日常的な作業にはピッタリなんだ。
ただし、ダーティリードは防げるけど、READ COMMITTEDは以下の問題からは守ってくれない:
- 「ノンリピータブルリード」(
Non-Repeatable Read):他のトランザクションがクエリの間にデータを変えたら、値が変わる可能性がある。 - 「ファントムリード」(
Phantom Read):他のトランザクションが新しい行を追加して、クエリ結果に影響を与えることがある。
READ COMMITTEDを使うときのコツ
必ずトランザクションを終わらせよう: COMMITやROLLBACKを忘れるとロックの問題が起きるから注意!
分離レベルが十分か確認しよう: トランザクション中にデータが変わらない保証が欲しいなら、REPEATABLE READを検討してみて。
インデックスを使おう: これでPostgreSQLがデータをもっと速く見つけて、変更も素早くできるよ。
例:注文処理
例えば、ordersっていう注文データを保存するテーブルがあるとする:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
);
INSERT INTO orders (customer_name, status)
VALUES ('Alice', 'pending'), ('Bob', 'pending');
「pending」状態の注文のステータスを更新したいとする:
BEGIN;
SELECT * FROM orders WHERE status = 'pending';
UPDATE orders SET status = 'completed' WHERE status = 'pending';
COMMIT;
もしこの処理の間に、他のトランザクションが新しい「pending」注文を追加してCOMMITしたら、自分のトランザクションではその行は見えない。なぜなら、読み始めた後に追加されたから。
これが「ファントムリード」の例。こういう状況を避けたいなら、SERIALIZABLEを使う必要がある。
READ COMMITTED分離レベルは、ほとんどのデータベース(PostgreSQL含む)でデフォルトの選択肢。ダーティリードから守ってくれるから、標準的な操作には十分いい選択。でも、もっと厳密な一貫性が必要なシナリオでは、より厳しい分離レベルが必要になることもある。どの分離レベルを使うかは、自分のタスクやパフォーマンス要件次第で決めよう!
GO TO FULL VERSION