PostgreSQLでの『could not serialize access due to concurrent update』を解決する方法

PostgreSQLでの『could not serialize access due to concurrent update』を解決する方法

このエラーは、PostgreSQLでトランザクションの分離レベルが「SERIALIZABLE」に設定されている際に、同時に複数のトランザクションが同じ行を更新しようとして競合が発生した場合に起こる。高い一貫性を保証するための制限だが、アプリケーション側で対策を取らないと頻発することがある。以下に、エラーの発生条件と具体的な対応策をまとめる。

エラーの発生例

ERROR: could not serialize access due to concurrent update

このエラーは、次のような条件で発生する。

  • 分離レベルが SERIALIZABLE に設定されている
  • 2つ以上のトランザクションが同時に同じ行を読み、更新しようとしている
  • PostgreSQLの内部ロック管理により、片方のトランザクションが中断される

分離レベル SERIALIZABLE の特徴

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

この分離レベルは、最も高い整合性を保証するが、並行性が犠牲になる。並列実行されたクエリの整合性が保てなくなる可能性があると判断された場合、PostgreSQLは明示的にエラーを返す。

再試行処理(retry logic)を組み込む

このエラーは一時的なものであるため、クライアントアプリケーション側で再試行を行うことで回避できる。

例:Python(psycopg2)での再試行処理

import psycopg2
import time

for attempt in range(5):
    try:
        with conn:
            with conn.cursor() as cur:
                cur.execute("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;")
                cur.execute("UPDATE users SET balance = balance - 100 WHERE id = 1;")
                conn.commit()
        break
    except psycopg2.errors.SerializationFailure:
        time.sleep(0.1)

分離レベルを下げる

競合が発生するような場面で必ずしも SERIALIZABLE が必要ない場合、REPEATABLE READREAD COMMITTED に変更することで、エラーを防げる。

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

ただし、整合性要件に応じて慎重に判断する。

トランザクションを短く保つ

トランザクションの実行時間が長いと、競合のリスクが増す。可能な限り以下のようにする:

  • トランザクション内では、必要最小限の処理のみを行う
  • I/Oや待機処理を避ける

処理順序を制御する

ロジックやクエリの順序を揃えることで、競合の発生確率を下げることができる。すべてのクライアントが同じ順序で行を処理するように設計する。

例:

SELECT * FROM tasks ORDER BY id FOR UPDATE;

ロックを明示的に使用する

競合を避けるために、SELECT ... FOR UPDATE で明示的にロックを取ってから更新する。

BEGIN;
SELECT * FROM items WHERE id = 10 FOR UPDATE;
UPDATE items SET stock = stock - 1 WHERE id = 10;
COMMIT;

これにより他のトランザクションが同じ行を触るのを防ぐ。

検知とログの強化

エラー発生を検知しやすくするために、アプリケーションログやDB監視で SerializationFailure をフィルタリング・通知する仕組みを設ける。

UPDATE対象のスコープを狭める

大量の行を一括更新すると競合が発生しやすい。必要な行だけを対象にする。

UPDATE orders
SET status = 'done'
WHERE id = 123 AND status = 'pending';

条件を付けて他トランザクションとの競合を減らす。

パーティショニングやキューイングによる分散処理

高負荷な並列更新が避けられない場合は、対象テーブルをパーティション化したり、ジョブキューで更新処理を直列化する手法が有効。

まとめ

  • PostgreSQLの SERIALIZABLE は厳格な整合性の代償として競合に敏感
  • アプリケーション側の再試行処理で回避が可能
  • 分離レベル、トランザクションの粒度、ロック戦略の見直しで安定性が向上