データベースを安全かつ効率的に操作するためには、トランザクション制御と排他制御(ロック)の理解が不可欠です。本章では、BEGIN / COMMIT / ROLLBACK を用いたトランザクションの基本操作から、ロックの種類とデッドロックの回避、分離レベルの違いまで、現場で活用できる知識を詳しく解説します。
トランザクションの基本操作
トランザクションとは、データベースにおける一連の処理を「1つのまとまり」として扱う仕組みです。成功すればすべてが反映され、失敗すればすべて取り消されます。これはACID特性(Atomicity, Consistency, Isolation, Durability)に基づいています。
基本的な構文
BEGIN; UPDATE accounts SET balance = balance - 10000 WHERE id = 1; UPDATE accounts SET balance = balance + 10000 WHERE id = 2; COMMIT; -- 正常終了で確定
もし途中でエラーが起きた場合は、次のように取り消します:
ROLLBACK; -- エラー時に元に戻す
これにより、整合性のあるデータ更新が可能になります。
ロックモードとデッドロック
ロックの種類
同時実行制御のため、DBは裏側で様々なロックを行います。主な種類は以下の通りです。
| ロックモード | 説明 | 用途 |
|---|---|---|
| 共有ロック(Shared Lock) | 読み取りを許可、書き込みはブロック | SELECT FOR SHARE |
| 排他ロック(Exclusive Lock) | 書き込みのための完全ロック | SELECT FOR UPDATE |
| 行ロック | 特定行のみをロック | 通常のUPDATE操作など |
| テーブルロック | テーブル全体をロック | 大量更新時やDDL操作 |
デッドロックとは?
デッドロックは、複数のトランザクションが互いに相手のロック解除を待ち続け、処理が永遠に進まない状態です。
-- トランザクションA BEGIN; UPDATE users SET name = 'A' WHERE id = 1; -- トランザクションB BEGIN; UPDATE users SET name = 'B' WHERE id = 2; -- Aがid=2、Bがid=1を更新しようとするとデッドロック
デッドロックの回避策
- 更新順序を統一する(ID順など)
- トランザクションを短く保つ
- リトライ処理を組み込む(アプリ側で再試行)
PostgreSQLやMySQLでは、デッドロックが検出されると自動的に一方のトランザクションが強制終了されます。そのため、アプリ側でリトライ処理を設けることが重要です。
トランザクション分離レベルの理解
複数のトランザクションが同時に走っている場合、どの程度まで互いの影響を受けるかを定めたものが分離レベル(Isolation Level)です。
| 分離レベル | 読み取りの一貫性 | 防止される問題 |
|---|---|---|
| Read Uncommitted | 未コミットのデータを読める | なし(最も危険) |
| Read Committed | コミット済のみ読める | ダーティリード防止 |
| Repeatable Read | 読み取り一貫性を保証 | ダーティリード、ファジーリード防止 |
| Serializable | 完全に直列化された処理 | すべての同時実行問題を防止 |
PostgreSQLのデフォルトは Read Committed です。
-- 分離レベルを変更する例(PostgreSQL) SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN; -- トランザクション処理 COMMIT;
レベルが高いほど安全性は増しますが、同時実行性能が低下するため、用途に応じて使い分けることが重要です。
まとめ
トランザクションと排他制御は、データの整合性を保ち、信頼性の高いアプリケーションを作るための基盤です。
- BEGIN / COMMIT / ROLLBACK を活用して確実なデータ操作を行う
- ロックとデッドロックの動作を理解して、安全な並行処理を設計する
- 分離レベルはパフォーマンスと整合性のトレードオフを理解して選択する
これらの知識を身につけることで、複雑なシステムでも安定性と性能を両立できるようになります。