PostgreSQL「current transaction is aborted, commands ignored until end of transaction block」エラー原因と解決方法

PostgreSQL「current transaction is aborted, commands ignored until end of transaction block」エラー原因と解決方法

ERROR:  current transaction is aborted, commands ignored until end of transaction block

PostgreSQLを使っていると必ず一度は遭遇するエラーです。特にORMや接続プールを使う環境では、エラーの原因が別の場所にあるのに「なぜこのSQLでエラーが出るの?」と混乱しやすいのが厄介です。

本記事では以下の疑問に答えます。

  • このエラーがなぜ出るのか
  • どこでエラーが起きたのかを特定する方法
  • psql・Python・SQLAlchemy・Django・Node.js・Javaでの対処法
  • 再発を防ぐためのトランザクション設計


目次

結論:先に答えを出す

-- ① まず ROLLBACK してトランザクションをリセット
ROLLBACK;

-- ② 原因となったエラーを特定・修正してから再実行
-- (エラーは必ず ROLLBACK より前のどこかで発生している)

-- ③ 部分的にやり直したい場合は SAVEPOINT を使う
BEGIN;
SAVEPOINT sp1;
INSERT INTO ...;  -- ← ここでエラー
ROLLBACK TO SAVEPOINT sp1;  -- エラー前の状態に戻る
INSERT INTO ...;  -- 修正後に再実行
COMMIT;

エラーの仕組みを理解する

PostgreSQL のトランザクションの挙動

PostgreSQLはトランザクション内でエラーが発生すると、そのトランザクション全体を「中断状態(aborted)」に置きます。中断状態のトランザクションでは後続のSQLがすべて無視され、同じエラーが繰り返されます。

BEGIN;

INSERT INTO users (name) VALUES ('Alice');  -- ✅ 成功

INSERT INTO users (id) VALUES ('not_a_number');  -- ❌ エラー発生
-- ERROR: invalid input syntax for type integer

-- この時点でトランザクションは「中断状態」になる

SELECT * FROM users;  -- ❌ このSQLは実行されない
-- ERROR: current transaction is aborted,
--        commands ignored until end of transaction block

INSERT INTO orders (...) VALUES (...);  -- ❌ これも実行されない
-- ERROR: current transaction is aborted,
--        commands ignored until end of transaction block

つまり「current transaction is aborted」は原因ではなく結果のエラーです。本当の原因は必ずその前のSQLにあります。

MySQL との違い

MySQLは1つのSQLでエラーが発生してもトランザクションを継続します。PostgreSQLの挙動に慣れていないMySQLユーザーが混乱しやすいポイントです。

PostgreSQLMySQL(InnoDB)
エラー後の動作トランザクション全体を中断そのSQLだけ失敗、継続可能
後続SQLすべて無視される実行される
回復方法ROLLBACK が必要そのまま継続可能

psql での対処法

基本:ROLLBACK してリセット

-- エラーが出た状態
=# SELECT * FROM users;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

-- ROLLBACK でリセット
=# ROLLBACK;
ROLLBACK

-- 再度実行できる状態になった
=# SELECT * FROM users;
 id | name
----+------
  1 | Alice

エラーの原因を特定する

-- psql のメッセージ詳細を確認する
\errverbose

-- トランザクション状態を確認
SELECT pg_current_xact_id_if_assigned();
-- NULL ならトランザクション外(正常)
-- 値があればトランザクション内

-- 実行中のトランザクションを確認
SELECT pid, state, query, xact_start
FROM pg_stat_activity
WHERE state = 'idle in transaction';

SAVEPOINT で部分的に回復する

エラーが発生しても一部の処理を保持したい場合に使います。

BEGIN;

INSERT INTO users (name) VALUES ('Alice');  -- 保持したい

SAVEPOINT before_risky_operation;

INSERT INTO users (id) VALUES ('invalid');  -- エラーになるSQL
-- ERROR: invalid input syntax for type integer

-- SAVEPOINT まで巻き戻す(Alice のINSERTは保持される)
ROLLBACK TO SAVEPOINT before_risky_operation;

-- 正しいSQLで再実行
INSERT INTO users (name) VALUES ('Bob');

COMMIT;  -- Alice と Bob の両方がコミットされる

psql の \set ON_ERROR_ROLLBACK 設定

-- ON_ERROR_ROLLBACK を有効にすると
-- エラー発生時に自動的に SAVEPOINT まで巻き戻す
\set ON_ERROR_ROLLBACK on

BEGIN;
INSERT INTO users (name) VALUES ('Alice');  -- 成功

INSERT INTO users (id) VALUES ('invalid');  -- エラー
-- ERROR: ...
-- psql が自動的に ROLLBACK TO SAVEPOINT する

SELECT * FROM users;  -- ← 続けて実行できる!

COMMIT;  -- Alice だけコミットされる
# psql 起動時にデフォルト設定
psql -v ON_ERROR_ROLLBACK=on データベース名

# .psqlrc に追記して恒久設定
echo "\set ON_ERROR_ROLLBACK on" >> ~/.psqlrc

Python(psycopg2)での対処法

エラーが出るパターン

import psycopg2

conn = psycopg2.connect("dbname=mydb")
cur = conn.cursor()

try:
    cur.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))
    cur.execute("INSERT INTO users (id) VALUES (%s)", ("invalid",))  # エラー
except Exception as e:
    print(f"エラー: {e}")

# ❌ トランザクションが中断状態のまま
cur.execute("SELECT * FROM users")
# InternalError: current transaction is aborted...

正しい対処法

import psycopg2

conn = psycopg2.connect("dbname=mydb")
cur = conn.cursor()

try:
    cur.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))
    cur.execute("INSERT INTO users (id) VALUES (%s)", ("invalid",))
    conn.commit()  # 成功時はコミット
except Exception as e:
    conn.rollback()  # ❌ エラー時は必ず ROLLBACK
    print(f"エラー: {e}")

# ✅ ROLLBACK 後は正常に実行できる
cur.execute("SELECT * FROM users")

context manager を使う(推奨)

import psycopg2
from contextlib import contextmanager

conn = psycopg2.connect("dbname=mydb")

# with 文でトランザクションを管理
with conn:  # エラー時は自動 ROLLBACK、成功時は自動 COMMIT
    with conn.cursor() as cur:
        cur.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))
        cur.execute("INSERT INTO users (name) VALUES (%s)", ("Bob",))

# 別のトランザクション
with conn:
    with conn.cursor() as cur:
        cur.execute("SELECT * FROM users")
        rows = cur.fetchall()

SAVEPOINT を使った部分的なエラー処理

conn = psycopg2.connect("dbname=mydb")
cur = conn.cursor()

cur.execute("BEGIN")
cur.execute("INSERT INTO users (name) VALUES (%s)", ("Alice",))

for item in items:
    try:
        cur.execute("SAVEPOINT sp")
        cur.execute("INSERT INTO orders (%s)", (item,))
    except Exception as e:
        cur.execute("ROLLBACK TO SAVEPOINT sp")
        print(f"スキップ: {item}: {e}")

cur.execute("COMMIT")

SQLAlchemy での対処法

エラーが出るパターン

from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session

engine = create_engine("postgresql://user:pass@localhost/mydb")

with Session(engine) as session:
    try:
        session.execute(text("INSERT INTO users (name) VALUES ('Alice')"))
        session.execute(text("INSERT INTO users (id) VALUES ('invalid')"))  # エラー
    except Exception as e:
        print(f"エラー: {e}")

    # ❌ セッションがエラー状態のまま
    result = session.execute(text("SELECT * FROM users"))
    # InvalidRequestError: Can't reconnect until invalid transaction is rolled back.

正しい対処法:セッションのロールバック

from sqlalchemy.orm import Session

with Session(engine) as session:
    try:
        session.execute(text("INSERT INTO users (name) VALUES ('Alice')"))
        session.execute(text("INSERT INTO users (id) VALUES ('invalid')"))
        session.commit()
    except Exception as e:
        session.rollback()  # ✅ 必ず rollback
        print(f"エラー: {e}")

    # ✅ rollback 後は正常に実行できる
    result = session.execute(text("SELECT * FROM users"))

SQLAlchemy 2.0 スタイル(推奨)

from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session

engine = create_engine("postgresql://user:pass@localhost/mydb")

# begin() を使うと自動コミット・ロールバック
with Session(engine) as session:
    with session.begin():  # エラー時は自動 rollback、成功時は自動 commit
        session.execute(text("INSERT INTO users (name) VALUES ('Alice')"))
        session.execute(text("INSERT INTO users (name) VALUES ('Bob')"))

ORM モデルでの対処

from sqlalchemy.orm import Session
from models import User, Order

def create_user_with_order(name: str, item: str):
    with Session(engine) as session:
        try:
            user = User(name=name)
            session.add(user)
            session.flush()  # IDを取得するために flush

            order = Order(user_id=user.id, item=item)
            session.add(order)

            session.commit()
            return user.id

        except Exception as e:
            session.rollback()
            raise  # 呼び出し元にエラーを伝える

接続プールと expire_on_commit

# SQLAlchemy のよくある落とし穴:
# commit 後にオブジェクトにアクセスすると lazy load が走り
# セッションが切れているとエラーになることがある

engine = create_engine(
    "postgresql://user:pass@localhost/mydb",
    pool_pre_ping=True,       # 接続前に死活確認
    pool_recycle=3600,        # 1時間で接続を再作成
)

# expire_on_commit=False にすると commit 後もオブジェクトにアクセスできる
with Session(engine, expire_on_commit=False) as session:
    with session.begin():
        user = User(name="Alice")
        session.add(user)
    # commit 後も user.id などにアクセスできる
    print(user.id)

Django での対処法

Django のトランザクション管理

Djangoはデフォルトで自動コミットモード(各クエリが独立したトランザクション)で動作します。

# settings.py で ATOMIC_REQUESTS を有効にすると
# リクエスト全体がトランザクションになる
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'ATOMIC_REQUESTS': True,  # リクエスト全体をトランザクションにする
    }
}

transaction.atomic() の使い方

from django.db import transaction

# ✅ 基本パターン
def create_user(name):
    with transaction.atomic():
        user = User.objects.create(name=name)
        Profile.objects.create(user=user)
        return user

# ❌ エラーが出るパターン(atomic なし)
def bad_example():
    User.objects.create(name="Alice")
    # ↑ ここでエラーが起きたとき、Aliceだけ保存されてしまう
    Profile.objects.create(user_id=999)  # 存在しないID

SAVEPOINT を使った部分的なエラー処理

from django.db import transaction

def bulk_create_users(user_list):
    created = []
    failed = []

    with transaction.atomic():
        for user_data in user_list:
            try:
                # SAVEPOINT を使って部分的にロールバック
                with transaction.atomic():
                    user = User.objects.create(**user_data)
                    created.append(user)
            except Exception as e:
                # この atomic ブロックだけ rollback される
                failed.append({"data": user_data, "error": str(e)})

    return created, failed

エラー後に再利用する場合

from django.db import connection, transaction

# トランザクションが中断状態かどうか確認
def is_transaction_aborted():
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
        return False
    except Exception:
        return True

# エラー後にリセットする
def reset_transaction():
    if connection.in_atomic_block:
        transaction.set_rollback(True)
    else:
        connection.close()

ATOMIC_REQUESTS と例外処理

# ATOMIC_REQUESTS=True の場合、ビュー全体がトランザクション
# 例外が発生するとリクエスト全体がロールバックされる

from django.db import transaction
from django.http import JsonResponse

def my_view(request):
    try:
        with transaction.atomic():
            # 処理
            user = User.objects.create(name=request.POST['name'])
            send_email(user)  # ここで例外が出ても DB はロールバック
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=400)

    return JsonResponse({'id': user.id})

Node.js(node-postgres / pg)での対処法

エラーが出るパターン

const { Pool } = require('pg');
const pool = new Pool();

async function badExample() {
  const client = await pool.connect();

  try {
    await client.query('BEGIN');
    await client.query("INSERT INTO users (name) VALUES ('Alice')");
    await client.query("INSERT INTO users (id) VALUES ('invalid')");  // エラー
    await client.query('COMMIT');
  } catch (e) {
    console.error(e);
    // ❌ ROLLBACK を忘れると接続がエラー状態のまま
  } finally {
    client.release();  // プールに戻すが、中断状態のまま
  }
}

正しい対処法

const { Pool } = require('pg');
const pool = new Pool();

async function goodExample() {
  const client = await pool.connect();

  try {
    await client.query('BEGIN');
    await client.query("INSERT INTO users (name) VALUES ($1)", ['Alice']);
    await client.query("INSERT INTO users (name) VALUES ($1)", ['Bob']);
    await client.query('COMMIT');

  } catch (e) {
    await client.query('ROLLBACK');  // ✅ 必ず ROLLBACK
    throw e;  // エラーを呼び出し元に伝える

  } finally {
    client.release();  // プールに戻す
  }
}

// トランザクションをラップするヘルパー関数
async function withTransaction(pool, callback) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const result = await callback(client);
    await client.query('COMMIT');
    return result;
  } catch (e) {
    await client.query('ROLLBACK');
    throw e;
  } finally {
    client.release();
  }
}

// 使い方
await withTransaction(pool, async (client) => {
  await client.query("INSERT INTO users (name) VALUES ($1)", ['Alice']);
  await client.query("INSERT INTO orders (user_id) VALUES ($1)", [1]);
});

Prisma での対処法

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

// Prisma は $transaction でアトミックな処理を保証
const [user, order] = await prisma.$transaction([
  prisma.user.create({ data: { name: 'Alice' } }),
  prisma.order.create({ data: { userId: 1, item: 'Book' } }),
]);

// インタラクティブなトランザクション
const result = await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ data: { name: 'Alice' } });
  const order = await tx.order.create({
    data: { userId: user.id, item: 'Book' },
  });
  return { user, order };
});

Java(JDBC / Spring)での対処法

JDBC での対処法

Connection conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false);  // トランザクション開始

try {
    PreparedStatement ps = conn.prepareStatement(
        "INSERT INTO users (name) VALUES (?)"
    );
    ps.setString(1, "Alice");
    ps.executeUpdate();

    // ❌ エラーになる SQL
    PreparedStatement ps2 = conn.prepareStatement(
        "INSERT INTO users (id) VALUES (?)"
    );
    ps2.setString(1, "invalid");
    ps2.executeUpdate();

    conn.commit();  // 成功時はコミット

} catch (SQLException e) {
    conn.rollback();  // ✅ 必ず ROLLBACK
    throw e;

} finally {
    conn.setAutoCommit(true);
    conn.close();
}

Spring(@Transactional)での対処法

import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    // ✅ @Transactional でトランザクション管理
    // RuntimeException 発生時は自動ロールバック
    @Transactional
    public User createUser(String name) {
        User user = userRepository.save(new User(name));
        orderRepository.save(new Order(user.getId()));
        return user;
    }

    // checked exception もロールバックしたい場合
    @Transactional(rollbackFor = Exception.class)
    public User createUserWithChecked(String name) throws Exception {
        User user = userRepository.save(new User(name));
        // checked exception もロールバック対象に
        return user;
    }

    // SAVEPOINT 相当:ネストしたトランザクション
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void optionalOperation() {
        // 失敗しても外側のトランザクションは継続
    }
}

接続プール使用時の注意点

接続プールを使う環境では、エラー状態の接続がプールに戻り、次のリクエストで使い回されることがあります。これが「なぜかランダムにエラーが出る」という症状につながります。

問題のパターン

1. リクエストA: 接続を取得 → エラー発生 → ROLLBACK せずにプールに返却
2. リクエストB: エラー状態の接続を取得 → 最初のSQLから「aborted」エラー
3. リクエストB: 原因不明のエラーとして混乱する

対策

# SQLAlchemy:pool_pre_ping で接続の死活確認
engine = create_engine(
    "postgresql://...",
    pool_pre_ping=True,  # 使用前に SELECT 1 で接続確認
)

# psycopg2:接続のステータスを確認してからプールに返す
if conn.status == psycopg2.extensions.STATUS_IN_TRANSACTION:
    conn.rollback()
pool.putconn(conn)
// node-postgres:エラー時は必ず ROLLBACK してから release
try {
  // ...
} catch (e) {
  await client.query('ROLLBACK');  // ← これを忘れない
} finally {
  client.release();
}

再発防止:トランザクション設計のベストプラクティス

1. トランザクションは短く保つ

-- ❌ 長すぎるトランザクション
BEGIN;
INSERT INTO orders ...;
-- (時間のかかる処理)
UPDATE inventory ...;
COMMIT;

-- ✅ 必要最小限の範囲にまとめる
-- 計算など時間のかかる処理はトランザクション外で行う
local_result = calculate_something();  -- トランザクション外
BEGIN;
INSERT INTO orders (result) VALUES (local_result);
UPDATE inventory ...;
COMMIT;

2. エラーハンドリングを必ず実装する

# ✅ try-except-rollback のパターンを徹底する
def safe_db_operation(conn):
    try:
        with conn.cursor() as cur:
            cur.execute("BEGIN")
            # 処理
            cur.execute("COMMIT")
    except Exception as e:
        conn.rollback()
        logger.error(f"DB エラー: {e}")
        raise

3. pg_stat_activity で孤立したトランザクションを監視する

-- 長時間 idle in transaction のセッションを確認
SELECT
  pid,
  usename,
  application_name,
  state,
  xact_start,
  now() - xact_start AS duration,
  left(query, 100) AS query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND xact_start < now() - interval '5 minutes'
ORDER BY xact_start;

-- 孤立したセッションを強制終了
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
  AND xact_start < now() - interval '1 hour';

4. タイムアウトを設定する

-- セッション単位でトランザクションタイムアウトを設定
SET idle_in_transaction_session_timeout = '5min';
SET statement_timeout = '30s';

-- postgresql.conf でシステム全体に設定
idle_in_transaction_session_timeout = '5min'
statement_timeout = '30s'
# SQLAlchemy でタイムアウトを設定
engine = create_engine(
    "postgresql://...",
    connect_args={
        "options": "-c idle_in_transaction_session_timeout=300000"
                   " -c statement_timeout=30000"
    }
)

トラブルシューティング

「原因のSQL」を特定できない

-- PostgreSQL のログを有効にして原因を追う
-- postgresql.conf
log_min_error_statement = error  -- エラーになったSQLをログに記録
log_min_duration_statement = 0   -- すべてのSQLをログに記録(開発時のみ)

-- ログを確認
tail -f /var/log/postgresql/postgresql-*.log | grep ERROR
# Python(SQLAlchemy)でSQLをログに出す
import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

接続プールで「ランダムに」エラーが出る

症状:同じコードなのに時々「current transaction is aborted」が出る
原因:ROLLBACK せずにプールに返した接続が再利用されている
# 確認:pg_stat_activity でエラー状態の接続がないか確認
SELECT pid, state, query
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)');

# 対策:接続取得時に状態をリセット
# SQLAlchemy の pool_pre_ping=True で自動対応
# または接続時に ROLLBACK を実行するイベントを設定
from sqlalchemy import event

@event.listens_for(engine, "connect")
def connect(dbapi_connection, connection_record):
    dbapi_connection.rollback()

Django で TransactionManagementError が出る

# 症状
# TransactionManagementError: An error occurred in the current transaction.
# You can't execute queries until the end of the 'atomic' block.

# 原因:atomic ブロック内でエラーが発生し、そのまま続けようとした

# 解決策:atomic ブロックをネストして部分ロールバック
from django.db import transaction

def bulk_process(items):
    results = []
    for item in items:
        try:
            with transaction.atomic():  # ネストした atomic
                result = process_item(item)
                results.append(result)
        except Exception as e:
            # 内側の atomic だけロールバック、外側は継続
            results.append({'error': str(e)})
    return results

よくある質問(FAQ)

Q1. ROLLBACK するとデータはすべて消えますか?

BEGIN 以降に実行したすべての変更が取り消されます。BEGIN の前にコミット済みのデータは影響を受けません。SAVEPOINT を使っていれば、その時点までの変更を保持できます。

Q2. autocommit モードにすれば解決しますか?

autocommit を有効にすると各SQLが即座にコミットされるため、このエラーは出なくなります。ただし、複数のSQLをアトミックに実行できなくなるため、データの整合性が保証できません。アプリケーションの要件に応じて慎重に判断してください。

# psycopg2 で autocommit を有効にする
conn.autocommit = True

# SQLAlchemy
engine = create_engine("postgresql://...", isolation_level="AUTOCOMMIT")

Q3. ROLLBACK を忘れたらどうなりますか?

セッションが切断されるまでトランザクションが中断状態のまま残ります。接続プールを使っている場合、その接続を再利用した他のリクエストでも同じエラーが出ます。セッションが切れると自動的にロールバックされます。

Q4. BEGIN を明示的に書いていないのにエラーが出ます

多くのフレームワークやORMはデフォルトでトランザクションを開始します。また、PostgreSQLは明示的な BEGIN がなくても、最初のSQLで暗黙的なトランザクションを開始します(autocommit=off の場合)。

# psycopg2 はデフォルトで autocommit=False
# → 最初のクエリで自動的に BEGIN される
conn = psycopg2.connect("...")
cur = conn.cursor()
cur.execute("SELECT 1")  # ← ここで暗黙的に BEGIN
# この状態でエラーが出ると aborted になる

Q5. 本番環境で孤立したトランザクションを緊急終了したい

-- 長時間 idle in transaction のセッションを終了
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction (aborted)'
   OR (state = 'idle in transaction'
       AND xact_start < now() - interval '10 minutes');

まとめ

「current transaction is aborted」は PostgreSQL のトランザクション管理の仕様に基づくエラーです。ポイントを整理します。

原因:

  • トランザクション内でエラーが発生した
  • エラーが出た後に ROLLBACK せず同じトランザクションでSQLを実行しようとした

対処:

-- psql:ROLLBACK してリセット
ROLLBACK;

-- 部分的に保持したい場合:SAVEPOINT を使う
ROLLBACK TO SAVEPOINT セーブポイント名;

アプリ別の対処:

環境対処法
psycopg2conn.rollback()
SQLAlchemysession.rollback()
Djangotransaction.atomic() でネスト
node-postgresawait client.query('ROLLBACK')
Spring@Transactional で自動管理

再発防止:

  • エラーハンドリングで必ず ROLLBACK する
  • 接続プール使用時は pool_pre_ping=True などで死活確認
  • idle_in_transaction_session_timeout でタイムアウトを設定
  • pg_stat_activity で孤立トランザクションを監視

参考リンク

公式ドキュメント

フレームワーク別ドキュメント

関連記事(本サイト)


本記事は2026年6月時点の情報をもとに、PostgreSQL 16・SQLAlchemy 2.0・Django 5.x・psycopg2 2.9.x での動作確認に基づき作成しています。バージョンによって挙動が異なる場合があるため、最新情報は各公式ドキュメントをご確認ください。