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_transaction là 64. Đ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 TABLEtườ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_countmỗ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-transactioncủa Flyway vàatomic = Falsecủ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_connectionstừ 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ăngmax_locks_per_transactiontươ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.

