Fix lỗi PostgreSQL 'current transaction is aborted, commands ignored until end of transaction block'

intermediate🐘 PostgreSQL2026-05-07| PostgreSQL 12–16, Linux/macOS/Windows, mọi client (psql, Python psycopg2, Node.js pg, Java JDBC)

Error Message

ERROR: current transaction is aborted, commands ignored until end of transaction block
#postgresql#transaction#xử lý lỗi#rollback

Tình huống xảy ra

Bạn đang thực thi các câu lệnh SQL bên trong một transaction — migration dữ liệu, batch insert, hoặc chỉ là một phiên psql thông thường — và một trong số đó thất bại. Lúc này mọi lệnh tiếp theo đều báo lỗi:

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

Không có gì hoạt động được nữa. SELECT, INSERT, UPDATE — tất cả đều bị từ chối. Postgres đã đóng băng transaction lại cho đến khi bạn xử lý lỗi một cách tường minh.

Tại sao lỗi này xảy ra

Postgres không tự đoán mò. Khi một câu lệnh bên trong transaction block thất bại, nó đánh dấu toàn bộ transaction là đã hủy và dừng lại tại đó. Không có lệnh nào tiếp theo được thực thi trong transaction đó — kể cả các SELECT chỉ đọc — cho đến khi bạn phát lệnh ROLLBACK.

Đây là quyết định thiết kế có chủ ý. Nếu tiếp tục trong trạng thái thực thi dở dang — một số lệnh đã thành công, một số thì không — dữ liệu sẽ rơi vào trạng thái không xác định. Các nguyên nhân phổ biến nhất:

  • Vi phạm ràng buộc (unique key, foreign key, not-null)
  • Lỗi ép kiểu dữ liệu (ví dụ: chèn 'abc' vào cột kiểu integer)
  • Chia cho số không
  • Bất kỳ lỗi runtime nào bên trong block BEGIN

Trong code ứng dụng, lỗi này xuất hiện khi một query bị crash nhưng code vẫn tiếp tục chạy mà không bắt lỗi. Query tiếp theo đụng vào transaction đã bị hủy — và bạn thấy thông báo này thay vì thông báo lỗi thực sự có ích.

Cách khắc phục nhanh: ROLLBACK và bắt đầu lại

Cách xử lý giống nhau trong mọi trường hợp — rollback transaction để xóa trạng thái lỗi:

ROLLBACK;

Sau đó, Postgres chấp nhận lệnh trở lại. Bắt đầu một transaction mới nếu cần:

BEGIN;
-- các câu lệnh của bạn ở đây
COMMIT;

Trong psql, lệnh ROLLBACK hoàn toàn an toàn. Nó chỉ hoàn tác công việc đã thực hiện bên trong transaction hiện tại (đã thất bại) — không ảnh hưởng gì ra ngoài phạm vi đó.

Dùng SAVEPOINT để phục hồi mà không cần rollback toàn bộ

Transaction có nhiều bước và không muốn làm lại từ đầu? Hãy dùng savepoint. Đặt một điểm lưu trước mỗi thao tác có rủi ro:

BEGIN;

INSERT INTO orders (id, user_id, amount) VALUES (1, 42, 99.99);

SAVEPOINT before_discount;

-- Lệnh này có thể thất bại (ví dụ: vi phạm ràng buộc)
INSERT INTO discounts (order_id, code) VALUES (1, 'INVALID_CODE');

-- Chỉ rollback về savepoint, không rollback toàn bộ transaction
ROLLBACK TO SAVEPOINT before_discount;

-- Tiếp tục bình thường
UPDATE orders SET status = 'confirmed' WHERE id = 1;

COMMIT;

Lệnh INSERT vào bảng orders vẫn được giữ nguyên. Chỉ có công việc sau savepoint mới bị hoàn tác.

Xử lý trong code ứng dụng

Phần lớn các trường hợp thực tế xảy ra trong code ứng dụng khi exception không được bắt đúng cách. Dưới đây là pattern đúng cho từng stack phổ biến.

Python (psycopg2)

import psycopg2

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

try:
    cur.execute("BEGIN")
    cur.execute("INSERT INTO users (email) VALUES (%s)", ("test@example.com",))
    cur.execute("INSERT INTO profiles (user_id) VALUES (%s)", (9999,))  # có thể thất bại
    conn.commit()
except psycopg2.Error as e:
    conn.rollback()  # <-- quan trọng: reset trạng thái transaction
    print(f"Transaction failed: {e}")
finally:
    cur.close()
    conn.close()

Nếu bỏ qua conn.rollback() trong khối except, mọi lần sử dụng tiếp theo của connection đó đều sẽ gặp lỗi transaction đã bị hủy tương tự.

Python (psycopg2) với context manager

with psycopg2.connect("dbname=mydb user=postgres") as conn:
    with conn.cursor() as cur:
        try:
            cur.execute("INSERT INTO users (email) VALUES (%s)", ("test@example.com",))
            conn.commit()
        except psycopg2.Error:
            conn.rollback()
            raise

Node.js (node-postgres / pg)

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

async function runTransaction() {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    await client.query('INSERT INTO users(email) VALUES($1)', ['test@example.com']);
    await client.query('INSERT INTO profiles(user_id) VALUES($1)', [9999]); // có thể thất bại
    await client.query('COMMIT');
  } catch (err) {
    await client.query('ROLLBACK');  // <-- reset transaction
    throw err;
  } finally {
    client.release();
  }
}

Java (JDBC)

try {
    conn.setAutoCommit(false);
    stmt.executeUpdate("INSERT INTO users (email) VALUES ('test@example.com')");
    stmt.executeUpdate("INSERT INTO profiles (user_id) VALUES (9999)");
    conn.commit();
} catch (SQLException e) {
    conn.rollback();  // <-- bắt buộc
    throw e;
} finally {
    conn.setAutoCommit(true);
}

Lưu ý: connection pooling

Connection pool tạo ra một dạng lỗi âm thầm rất nguy hiểm. Một connection được trả về pool trong khi vẫn đang ở trạng thái transaction bị hủy sẽ "lây" sang request tiếp theo nhận được connection đó — dù request đó hoàn toàn không liên quan đến lỗi ban đầu. Nó chỉ đơn giản là thừa hưởng một connection bị hỏng.

Luôn rollback trước khi trả connection về pool. Hầu hết các thư viện pool xử lý việc này tự động nếu được cấu hình đúng — nhưng hãy kiểm tra lại:

  • HikariCP: mặc định đặt autoCommit=true, tự reset trạng thái khi trả về pool
  • psycopg2 pool: gọi pool.putconn(conn, close=True) nếu connection đang ở trạng thái lỗi
  • PgBouncer: chế độ transaction không tự reset trạng thái — luôn rollback tường minh trước khi giải phóng connection

Xác nhận đã khắc phục xong

Sau khi rollback, hãy kiểm tra xem connection đã sạch chưa:

-- Trong psql, lệnh này phải trả về không có lỗi:
SELECT 1;

-- Kiểm tra trạng thái transaction hiện tại
SELECT pg_current_xact_id_if_assigned();
-- Trả về NULL nếu không có transaction đang hoạt động (trạng thái sạch)

Trong code ứng dụng, chạy một query kiểm tra nhanh sau khi rollback trước khi tiếp tục:

cur.execute("SELECT 1")
# Nếu lệnh này thành công, connection đã sạch

Phòng tránh lỗi tái diễn

  • Bọc mọi transaction trong try/catch với ROLLBACK tường minh — không bao giờ để exception lan ra ngoài mà không rollback trước
  • Dùng savepoint cho các transaction nhiều bước khi chấp nhận được việc thất bại một phần
  • Bật autocommit cho các workload chỉ đọc — không cần transaction block nếu bạn chỉ đọc dữ liệu
  • Validate dữ liệu trước khi insert — phát hiện giá trị sai trong code ứng dụng sẽ gọn hơn là để đụng vào ràng buộc DB giữa chừng transaction
  • Ghi log lỗi gốc, không chỉ lỗi này — thông báo transaction bị hủy chỉ là triệu chứng; nguyên nhân thực sự là lỗi xuất hiện ngay trước đó

Related Error Notes