Transaksi PostgreSQL hanya-baca yang terbuka dapat memblokir DROP TABLE karena SELECT-nya memegang kunci tabel ACCESS SHARE hingga transaksi berakhir. Penghapusan memerlukan kunci ACCESS EXCLUSIVE yang bertentangan, sehingga harus menunggu—bahkan setelah kueri mengembalikan hasil. PostgreSQL: Penguncian Eksplisit
Panduan ini menunjukkan cara mereproduksi penantian tersebut dari Mac, mengidentifikasi sesi yang memblokir, dan menutup transaksi dengan benar di node-postgres. Panduan ini hanya menggunakan baris artikel sintetis. SQL-nya menargetkan PostgreSQL 18, versi yang disajikan dalam dokumentasi resmi terkini yang diambil untuk artikel ini; lihat manual yang sesuai dengan versi server Anda sebelum menerapkan pengaturan khusus versi.
Apa arti hanya-baca bagi kunci PostgreSQL?
READ ONLY membatasi perintah yang dapat dijalankan oleh suatu transaksi. Misalnya, mode ini melarang perubahan pada tabel biasa dan perintah seperti DROP TABLE. Mode ini tidak mengecualikan operasi baca dari penguncian tabel. Dalam skenario ini, pembaca dan sesi yang mencoba menghapus tabel berada dalam transaksi yang terpisah. PostgreSQL: SET TRANSACTION
Perintah SELECT biasa secara otomatis memperoleh ACCESS SHARE pada tabel yang dirujuk. DROP TABLE memerlukan ACCESS EXCLUSIVE, yang bertentangan dengan kunci tersebut. Biasanya, kunci bertahan hingga transaksi berakhir; pengecualian yang didokumentasikan adalah melakukan rollback ke savepoint yang dibuat sebelum kunci diperoleh. Contoh di bawah ini tidak menggunakan savepoint. PostgreSQL: Penguncian Eksplisit
Perbedaan utamanya adalah antara selesainya kueri dan selesainya transaksi. Melihat baris artikel di terminal berarti kueri telah mengembalikan hasil. Periksa apakah transaksi yang membungkusnya juga telah berakhir. Di psql, autocommit biasanya aktif, tetapi BEGIN eksplisit membuat transaksi tetap terbuka sampai Anda menyelesaikannya. PostgreSQL: psql
Bagaimana cara mereproduksi penghapusan yang terblokir di Mac?
Gunakan basis data uji PostgreSQL 18 lokal yang sudah ada, dengan psql tersedia di Terminal Mac Anda. Buka tiga tab terminal yang terhubung ke basis data yang sama. Namai tab tersebut A, B, dan C. Tab-tab ini harus menggunakan koneksi terpisah; memisahkan panel editor saja tidak menjaminnya.
Perintah koneksi berikut mengasumsikan nama basis data uji lokal Anda adalah postgres. Jika perlu, ganti dengan nama basis data uji yang sudah ada. Jalankan perintah ini di setiap tab. Opsi -X mencegah pemuatan berkas awal pribadi psql. PostgreSQL: psql
psql -X -d postgres
Di setiap koneksi, aktifkan autocommit secara eksplisit dan periksa versi server. Blok-blok di bawah merupakan panduan berurutan: blok berikutnya bergantung pada penyiapan dan status sesi yang dibuat sebelumnya.
\set AUTOCOMMIT on
SHOW server_version;
Buat tabel artikel sintetis
Di tab C, jalankan penyiapan sekali. Gunakan nama tabel yang belum dipakai: jika public.article_lock_demo sudah ada, berhenti dan pilih nama lain yang digunakan secara konsisten di seluruh contoh. Penyiapan ini sengaja tidak menghapus tabel yang sudah ada.
CREATE TABLE public.article_lock_demo (
id integer PRIMARY KEY,
title text NOT NULL
);
INSERT INTO public.article_lock_demo (id, title)
VALUES
(1, 'Choosing a notebook'),
(2, 'Organizing a reading list');
Biarkan transaksi pembaca tetap terbuka
Di tab A, jalankan blok ini dan biarkan koneksi berada di prompt. Jangan commit dulu.
SET application_name = 'article_reader_demo';
BEGIN READ ONLY;
SHOW transaction_read_only;
SELECT id, title
FROM public.article_lock_demo
ORDER BY id;
transaction_read_only seharusnya menampilkan on, diikuti dua baris sintetis. Operasi baca sudah selesai, tetapi transaksi eksplisitnya tetap terbuka.
Coba hapus tabel dari koneksi lain
Di tab B, jalankan blok berikut. Kirim DROP TABLE sebagai pernyataan terakhir, tanpa menambahkan perintah lain.
SET application_name = 'article_drop_demo';
BEGIN;
SET LOCAL lock_timeout = '60s';
DROP TABLE public.article_lock_demo;
Penghapusan seharusnya menunggu selama A tetap terbuka. Batas ilustratif 60 detik memberi Anda waktu untuk memeriksa penantian; ini bukan pengaturan universal yang direkomendasikan. lock_timeout membatalkan pernyataan yang terlalu lama menunggu untuk memperoleh kunci dan diterapkan secara terpisah pada setiap upaya memperoleh kunci. PostgreSQL: Default Koneksi Klien
Biarkan transaksi eksplisit B tetap terbuka setelah penghapusan selesai. Nanti Anda akan melakukan rollback agar tabel demonstrasi tetap ada, sekalipun penghapusan berhasil. DROP TABLE menghapus tabel beserta indeks dan batasan terkait saat transaksinya di-commit. PostgreSQL: DROP TABLE
Bagaimana membuktikan sesi mana yang memblokir penghapusan?
Saat B sedang menunggu, jalankan kueri berikutnya di C. Kueri ini mengidentifikasi dua koneksi demonstrasi dan menanyakan kepada PostgreSQL sesi mana yang memblokir permintaan kunci setiap koneksi. pg_blocking_pids() mengembalikan larik ID proses pemblokir, atau larik kosong jika proses tersebut tidak terblokir. PostgreSQL: Fungsi dan Operator Informasi Sistem
SELECT
pid,
application_name,
state,
xact_start,
wait_event_type,
wait_event,
pg_blocking_pids(pid) AS blocking_pids,
query
FROM pg_stat_activity
WHERE datname = current_database()
AND application_name IN (
'article_reader_demo',
'article_drop_demo'
)
ORDER BY application_name;
A seharusnya berstatus idle in transaction: transaksinya masih terbuka, tetapi tidak sedang menjalankan kueri. B dapat menampilkan active saat menunggu, dengan jenis peristiwa tunggu Lock. PostgreSQL mendokumentasikan bahwa status eksekusi dan peristiwa tunggu bersifat terpisah, sehingga active saja tidak membuktikan bahwa suatu perintah sedang berjalan. PostgreSQL: Sistem Statistik Kumulatif
Selanjutnya, periksa permintaan kunci tabel yang sebenarnya. Jalankan kueri ini di C sebelum mengakhiri transaksi A.
SELECT
a.application_name,
l.pid,
l.mode,
l.granted
FROM pg_locks AS l
JOIN pg_stat_activity AS a ON a.pid = l.pid
WHERE l.locktype = 'relation'
AND l.database = (
SELECT oid
FROM pg_database
WHERE datname = current_database()
)
AND l.relation = 'public.article_lock_demo'::regclass
ORDER BY a.application_name, l.mode;
Pola yang diharapkan di bawah ini hanya ilustrasi, bukan keluaran eksekusi yang direkam. Di pg_locks, granted = true berarti proses memegang kunci; false berarti proses sedang menunggunya. PostgreSQL: pg_locks
| Sesi | Mode kunci | granted |
Interpretasi |
|---|---|---|---|
article_reader_demo |
AccessShareLock |
true |
Pembaca memegang kunci tabelnya |
article_drop_demo |
AccessExclusiveLock |
false |
Penghapusan menunggu kunci tabelnya |
Gunakan kedua kueri itu sebagai rangkaian diagnosis: pertama, pastikan hubungan pemblokirannya, lalu periksa kunci yang dipegang dan diminta pada tabel terkait. Sesi yang tidak aktif saja bukan bukti yang memadai; hubungan dan baris kunci tersebut mengaitkannya dengan penghapusan ini.
Jika B sudah mengalami batas waktu, permintaan kuncinya yang sedang menunggu tidak akan muncul lagi. Jalankan ROLLBACK; di B, ulangi blok transaksi B, lalu periksa segera selagi A masih terbuka.
Bagaimana cara melepas kunci pembaca dan menyelesaikan demonstrasi?
Di A, akhiri transaksi hanya-baca:
COMMIT;
Jika B masih menunggu dan A satu-satunya pemblokirnya, penghapusan kini dapat selesai. Di B, segera batalkan penghapusan demonstrasi itu agar tabel sintetis tetap tersedia:
ROLLBACK;
Jika B mengalami batas waktu sebelum Anda melakukan commit pada A, jalankan ROLLBACK; di B terlebih dahulu. Setelah A berakhir, ulangi blok B lalu jalankan ROLLBACK; sekali lagi setelah penghapusan berhasil. Cara ini menangani kedua kemungkinan tanpa membiarkan transaksi B belum selesai.
Panduan keputusan berikut membantu membedakan masalah masa hidup transaksi dari penjelasan lain. Label aktivitas mengikuti status sesi yang didokumentasikan PostgreSQL. PostgreSQL: Sistem Statistik Kumulatif
| Pengamatan | Tindakan berikutnya |
|---|---|
A berstatus idle in transaction, dan larik pemblokir B memuat PID A |
Akhiri transaksi A dari koneksi A |
| A sudah berakhir, tetapi B masih melaporkan PID pemblokir | Periksa pemblokir yang tersisa |
| B melaporkan batas waktu kunci | Lakukan rollback pada transaksi eksplisit B sebelum mencoba lagi |
| B mengembalikan kesalahan dependensi | Periksa dependensi, bukan menganggapnya sebagai penantian kunci |
| B langsung mengembalikan hasil | Periksa apakah transaksi pembaca yang dimaksud masih terbuka |
IF EXISTS menangani tabel yang tidak ada, sedangkan CASCADE mengatur penghapusan objek dependen. Keduanya bukan opsi penantian kunci. Untuk latihan ini, gunakan perilaku dependensi bawaan; sintaks resmi DROP TABLE tidak memiliki varian CONCURRENTLY. PostgreSQL: DROP TABLE
Bagaimana node-postgres sebaiknya menutup transaksi hanya-baca?
Dengan node-postgres, gunakan satu klien yang telah diambil dari pool untuk BEGIN, setiap pernyataan dalam transaksi, serta COMMIT atau ROLLBACK terakhirnya. Dokumentasi transaksi resmi secara eksplisit memperingatkan agar tidak menggunakan pool.query() untuk transaksi karena pernyataannya dapat dikirim ke klien yang berbeda. node-postgres: Transaksi
Perlakukan penyelesaian transaksi dan pelepasan klien sebagai dua langkah aplikasi yang terpisah. client.release() mengembalikan klien yang telah diambil ke pool-nya; selesaikan transaksi sebelum pemanggilan itu. Jika rollback gagal, API pool mendukung penghancuran klien dengan memberikan argumen bernilai benar ke release(). node-postgres: API Pool
Untuk program lengkap ini, pertahankan tabel sintetis dari panduan di atas. Gunakan runtime Node.js terpasang yang mendukung modul ES dan AggregateError, lalu pasang pg di direktori proyek kerja:
npm install pg
Simpan kode berikut sebagai read-articles.mjs. Atur koneksinya ke basis data uji lokal yang sama dengan yang digunakan oleh psql. node-postgres mendukung variabel lingkungan koneksi PostgreSQL, termasuk PGDATABASE. node-postgres: Menghubungkan
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({ connectionTimeoutMillis: 5000 });
async function main() {
try {
const client = await pool.connect();
let discard = false;
try {
await client.query('BEGIN READ ONLY');
const result = await client.query(
'SELECT id, title FROM public.article_lock_demo ORDER BY id'
);
await client.query('COMMIT');
console.table(result.rows);
} catch (error) {
try {
await client.query('ROLLBACK');
} catch (rollbackError) {
discard = true;
throw new AggregateError(
[error, rollbackError],
'Query and rollback failed'
);
}
throw error;
} finally {
client.release(discard);
}
} finally {
await pool.end();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
PGDATABASE=postgres node read-articles.mjs
Program ini melakukan commit sebelum menampilkan baris, melakukan rollback jika terjadi kesalahan, dan melepaskan klien pada kedua kondisi. Sintaks JavaScript-nya diperiksa dengan Node.js; panduan basis datanya dicocokkan dengan dokumentasi, tetapi tidak dijalankan di sini karena PostgreSQL tidak tersedia di lingkungan riset.
Untuk satu operasi baca yang berdiri sendiri, pool.query() adalah cara praktis yang didokumentasikan dan tidak memerlukan pengambilan klien secara manual. Pilih transaksi eksplisit jika tugas Anda membutuhkannya, dan lakukan pemrosesan berkas atau interaksi desktop yang tidak terkait di luar masa hidup transaksi tersebut. node-postgres: API Pool
Batas waktu mana yang menangani pembaca yang belum menyelesaikan transaksi?
Gunakan batas waktu sesuai dengan apa yang sedang menunggu. lock_timeout membatasi waktu tunggu perolehan kunci untuk penghapusan. statement_timeout membatasi eksekusi pernyataan, sehingga pengaturan ini tidak menutup transaksi hanya karena aplikasi berhenti mengirim perintah setelah pembacaan berhasil. idle_in_transaction_session_timeout secara khusus mengakhiri sesi yang dibiarkan tidak aktif di dalam transaksi terbuka. Semua ini adalah pengaturan PostgreSQL 18 yang terdokumentasi; pilih durasi sesuai dengan pekerjaan yang diharapkan aplikasi Anda. PostgreSQL: Default Koneksi Klien
Untuk tugas ini, pemeriksaan penentunya adalah apakah transaksi pembaca sudah berakhir. Pastikan PID pemblokir, periksa kunci tabel, dan selesaikan transaksi pada koneksi yang memilikinya. Di node-postgres, jadikan perintah transaksi terakhir bagian dari alur keberhasilan dan kesalahan yang normal agar pembacaan yang selesai tidak membuat penghapusan tabel berikutnya menunggu.
Sumber
- 13.3. Penguncian Eksplisit — PostgreSQL Global Development Group
- SET TRANSACTION — PostgreSQL Global Development Group
- psql — PostgreSQL Global Development Group
- 19.11. Default Koneksi Klien — PostgreSQL Global Development Group
- DROP TABLE — PostgreSQL Global Development Group
- 9.27. Fungsi dan Operator Informasi Sistem — PostgreSQL Global Development Group
- 27.2. Sistem Statistik Kumulatif — PostgreSQL Global Development Group
- 53.13. pg_locks — PostgreSQL Global Development Group
- Transaksi — node-postgres
- API Pool — node-postgres
- Menghubungkan — node-postgres