Fix PostgreSQL 'ERROR: out of shared memory' Khi Quá Nhiều Lock hoặc Bảng Tạm

intermediate🐘 PostgreSQL2026-03-25| PostgreSQL 12–16, Linux/Ubuntu/Debian, cũng xảy ra trên macOS và Docker

Error Message

ERROR: out of shared memory
#postgresql#shared-memory#max-locks-per-transaction#cấu-hình

Lỗi Gặp Phải

ERROR: out of shared memory
HINT: You might need to increase max_locks_per_transaction.

Gặp lỗi này trong một lần migration lớn? Bạn không đơn độc. Lỗi xuất hiện khi một transaction duy nhất cố gắng giữ nhiều lock hơn số slot mà PostgreSQL có thể theo dõi — các migration đụng đến hàng trăm bảng, job ETL tạo hàng loạt bảng tạm, hoặc bảng phân vùng với 200+ partition đều là thủ phạm phổ biến. Gợi ý mà PostgreSQL đưa ra hoàn toàn chính xác: max_locks_per_transaction hầu như luôn là vấn đề.

Nguyên Nhân Gốc Rễ

Khi khởi động, PostgreSQL cấp phát trước một bảng lock cố định trong shared memory. Kích thước của nó được xác định bởi:

max_locks_per_transaction × (max_connections + max_prepared_transactions)

Giá trị mặc định của max_locks_per_transaction64. Điều đó có nghĩa là nếu một transaction lock hơn 64 đối tượng — bảng, index, sequence, mỗi cái được đếm riêng — PostgreSQL sẽ cạn kiệt bảng lock và ném ra lỗi này.

Những gì thường kích hoạt lỗi:

  • Các migration của Django hoặc Rails thêm index và constraint trên nhiều bảng trong một transaction
  • Các changeset của Liquibase hoặc Flyway chạy trên schema lớn (50+ bảng)
  • Các job ETL hàng loạt tạo hàng trăm bảng tạm mà không xóa chúng một cách tường minh
  • Bảng phân vùng — mỗi partition là một đối tượng lock riêng, nên một bảng với 200 partition theo tháng tiêu tốn 200 slot cho mỗi truy vấn
  • Các lệnh LOCK TABLE tường minh chồng chất bên trong một transaction dài

Cách Sửa 1: Tăng max_locks_per_transaction (Cách Chính)

Bắt đầu bằng cách kiểm tra giá trị hiện tại:

SHOW max_locks_per_transaction;

Sau đó chỉnh sửa postgresql.conf. Không biết file của bạn nằm ở đâu? Hỏi trực tiếp PostgreSQL:

SELECT current_setting('config_file');

-- Các đường dẫn phổ biến:
-- Ubuntu/Debian: /etc/postgresql/16/main/postgresql.conf
-- Docker: /var/lib/postgresql/data/postgresql.conf

Tăng giá trị lên:

# postgresql.conf
max_locks_per_transaction = 256   # tăng từ mặc định 64

Cài đặt này yêu cầu khởi động lại hoàn toàn — chỉ reload là không đủ:

# systemd
sudo systemctl restart postgresql

# Docker
docker restart your_postgres_container

# pg_ctl
pg_ctl restart -D /var/lib/postgresql/data

Xác nhận thay đổi đã được áp dụng:

SHOW max_locks_per_transaction;
-- nên trả về 256

Cách Sửa 2: Giảm Số Lock Trong Code

Trên các database được quản lý — RDS, Cloud SQL, Supabase — bạn thường không thể chỉnh postgresql.conf. Trong những trường hợp đó, hãy giảm số lượng lock mà code của bạn chiếm ngay từ đầu.

Chia nhỏ các migration lớn thành nhiều transaction nhỏ hơn:

-- Tệ: một transaction đụng đến 200 bảng
-- Tốt: mỗi transaction xử lý 20–30 bảng

BEGIN;
ALTER TABLE orders ADD COLUMN processed_at TIMESTAMPTZ;
ALTER TABLE order_items ADD COLUMN unit_cost NUMERIC(10,2);
-- giữ dưới ~50 thao tác mỗi transaction
COMMIT;

Xóa bảng tạm một cách tường minh trước khi tạo bảng mới:

DROP TABLE IF EXISTS tmp_processing;
CREATE TEMP TABLE tmp_processing AS
  SELECT id FROM orders WHERE status = 'pending';

Dùng ON COMMIT DROP để bảng tạm tự dọn dẹp:

BEGIN;
CREATE TEMP TABLE batch_data (
  id BIGINT
) ON COMMIT DROP;  -- tự động xóa khi COMMIT, lock được giải phóng ngay

INSERT INTO batch_data SELECT ...;
-- thực hiện công việc của bạn
COMMIT;  -- bảng tạm đã biến mất

Cách Sửa 3: Đối Với Bảng Phân Vùng

Bảng phân vùng là nguồn gây ra lỗi này theo cách rất khó phát hiện. Mỗi partition giữ lock riêng của nó, nên một truy vấn đụng đến event log có 365 partition sẽ lock 365+ đối tượng trước khi thực hiện bất kỳ công việc thực sự nào.

-- Kiểm tra bảng của bạn có bao nhiêu partition
SELECT count(*)
FROM pg_inherits
WHERE inhparent = 'events'::regclass;

Quy tắc ngón tay cái: đặt max_locks_per_transaction ít nhất bằng 2 × số_partition.

# Bảng với 365 partition theo ngày:
max_locks_per_transaction = 1024

Cũng hãy xác nhận rằng partition pruning đang được bật — mặc định là bật trong PG 12+ và giúp planner bỏ qua các partition không khớp với mệnh đề WHERE trong truy vấn của bạn:

SHOW enable_partition_pruning;
-- nên trả về 'on'

Cách Sửa 4: ALTER SYSTEM (Không Cần Sửa File)

Có quyền superuser nhưng không muốn SSH vào để chỉnh file cấu hình? Dùng ALTER SYSTEM:

ALTER SYSTEM SET max_locks_per_transaction = 256;

-- pg_reload_conf() KHÔNG đủ cho cài đặt này
SELECT pg_reload_conf();  -- bỏ qua bước này
-- Bạn vẫn cần khởi động lại hoàn toàn

Kiểm Tra Kết Quả

Sau khi khởi động lại, xác nhận cài đặt và quan sát mức sử dụng lock thực tế:

-- Xác nhận giá trị mới
SELECT name, setting, unit
FROM pg_settings
WHERE name = 'max_locks_per_transaction';

-- Snapshot các lock hiện tại (chạy trong khi thao tác gây lỗi đang diễn ra)
SELECT relation::regclass, mode, granted, pid
FROM pg_locks
WHERE relation IS NOT NULL
ORDER BY pid, relation;

-- Mỗi backend đang giữ bao nhiêu lock?
SELECT pid, count(*) AS lock_count
FROM pg_locks
WHERE relation IS NOT NULL
GROUP BY pid
ORDER BY lock_count DESC;

Tái hiện lỗi ban đầu, chạy truy vấn cuối cùng đó trong khi thao tác đang diễn ra, và bạn sẽ thấy số lock chính xác — từ đó biết chính xác giá trị cần đặt thay vì đoán mò.

Phòng Tránh

  • Theo dõi mức sử dụng bảng lock một cách chủ động: Số lock_count mỗi backend thường xuyên leo lên trên 50 là dấu hiệu cảnh báo. Hãy tăng giới hạn trước khi chạm trần.
  • Phân vùng có chừng mực: Phân vùng theo tháng đáp ứng được hầu hết các trường hợp. Phân vùng theo ngày cho tập dữ liệu 3 năm có nghĩa là 1.000+ đối tượng lock — gánh nặng lớn cho mỗi transaction.
  • Chạy các migration nặng ngoài transaction: Flag --no-transaction của Flyway và atomic = False của Django đều cho phép bỏ qua transaction bao bọc trên các migration đụng đến nhiều bảng. Hoặc chia thành các batch nhỏ hơn — 30 bảng mỗi transaction là ngưỡng an toàn.
  • Tính đến max_connections trong phép tính của bạn: Bảng lock tăng theo số lượng kết nối. Tăng gấp đôi max_connections từ 100 lên 200 và mỗi transaction thực chất chỉ còn một nửa số slot lock trừ khi bạn tăng max_locks_per_transaction tương ứng.

Quản lý cấu hình PostgreSQL trên nhiều môi trường? YAML ↔ JSON Converter tại ToolCraft rất tiện lợi để kiểm tra các file cấu hình định dạng YAML — phổ biến trong các thiết lập Docker Compose. Chạy hoàn toàn trên trình duyệt, không upload dữ liệu.

Related Error Notes