トランザクションとは何か
データベースにおけるトランザクションは、複数のデータベース操作を一つの論理的な作業単位として扱う仕組みです。銀行の送金処理を想像してください。口座Aから1万円を引き落とし、口座Bに1万円を入金する — この2つの操作は必ず両方成功するか、両方失敗する必要があります。片方だけが成功してしまうと、お金が消えたり増えたりしてしまいます。
トランザクションは、このような「全か無か」の原則を保証することで、データの整合性と信頼性を維持します。
トランザクションの基本的な流れ
1. BEGIN(トランザクション開始)
BEGIN TRANSACTION;
-- または
START TRANSACTION;
この時点から、以降の操作はトランザクションの一部として記録されます。
2. 一連のデータ操作
UPDATE accounts SET balance = balance - 10000 WHERE account_id = 'A001';
UPDATE accounts SET balance = balance + 10000 WHERE account_id = 'B002';
INSERT INTO transaction_log (from_account, to_account, amount, timestamp)
VALUES ('A001', 'B002', 10000, NOW());
3. COMMIT(確定)または ROLLBACK(取り消し)
-- すべて成功した場合
COMMIT;
-- エラーが発生した場合
ROLLBACK;
ACID特性:トランザクションの4つの保証
Atomicity(原子性)
トランザクション内の操作は「すべて成功」か「すべて失敗」のどちらかです。部分的な成功は許されません。システムクラッシュが発生しても、中途半端な状態にはなりません。
Consistency(一貫性)
トランザクション前後でデータベースは常に一貫した状態を保ちます。例えば、「口座残高は常に0以上」といった制約が守られます。
Isolation(独立性)
同時に実行される複数のトランザクションは互いに干渉しません。各トランザクションは、他のトランザクションが存在しないかのように動作します。
Durability(永続性)
一度コミットされたトランザクションの結果は、システム障害が発生しても失われません。電源が落ちても、ディスクが故障しない限りデータは保持されます。
実践例:ECサイトの注文処理
BEGIN TRANSACTION;
-- 1. 在庫を減らす
UPDATE inventory
SET quantity = quantity - 2
WHERE product_id = 'PRD001' AND quantity >= 2;
-- 2. 注文レコードを作成
INSERT INTO orders (user_id, product_id, quantity, total_price)
VALUES (123, 'PRD001', 2, 5000);
-- 3. 決済処理(外部APIと連携する場合もある)
INSERT INTO payments (order_id, amount, status)
VALUES (LAST_INSERT_ID(), 5000, 'completed');
-- すべて成功したら確定
COMMIT;
もし在庫が不足していたり、決済が失敗した場合は、ROLLBACK
によってすべての変更が取り消されます。
分離レベル:並行性と一貫性のトレードオフ
トランザクションの分離レベルは、並行実行時の挙動を制御します:
- READ UNCOMMITTED:最も低い分離レベル。ダーティリードが発生する可能性あり
- READ COMMITTED:コミットされたデータのみ読み取り可能
- REPEATABLE READ:同一トランザクション内では同じデータを読む
- SERIALIZABLE:最も高い分離レベル。完全な分離を保証
NoSQLデータベースにおけるトランザクション
DynamoDBの場合
DynamoDBはNoSQLデータベースとして、RDBMSとは異なるアプローチを取っています:
- TransactWriteItems API:最大100項目まで、複数のテーブルにまたがるトランザクション書き込みが可能
- TransactGetItems API:複数項目の一貫性のある読み取りを保証
- 条件付き書き込みによる楽観的ロック制御
# DynamoDBのトランザクション例
response = dynamodb.transact_write_items(
TransactItems=[
{
'Update': {
'TableName': 'Users',
'Key': {'user_id': {'S': 'user123'}},
'UpdateExpression': 'ADD points :points',
'ExpressionAttributeValues': {':points': {'N': '100'}}
}
},
{
'Put': {
'TableName': 'PointHistory',
'Item': {
'user_id': {'S': 'user123'},
'points': {'N': '100'},
'timestamp': {'S': '2024-01-20T10:00:00Z'}
}
}
}
]
)
まとめ:トランザクションを使いこなすために
トランザクションは、データの整合性を保証する強力な仕組みです。しかし、過度に長いトランザクションはパフォーマンスの低下やデッドロックの原因となります。以下の点に注意して設計しましょう:
- トランザクションは必要最小限の範囲に留める
- 適切な分離レベルを選択する
- デッドロックに備えたリトライ処理を実装する
- NoSQLを使う場合は、その特性に合わせた設計を行う
データベースの種類や要件に応じて、最適なトランザクション戦略を選択することが、堅牢なシステム構築の鍵となります。