Snapshot PostgreSQL yang lama boleh mengekalkan versi baris perantaraan yang tidak pernah dibacanya. Vacuum menggunakan horizon transaksi yang konservatif untuk menentukan perkara yang boleh dialih keluar. Panduan ini menerangkan sebabnya, kemudian menghasilkan semula tingkah laku tersebut dengan baris artikel sintetik dan node-postgres pada Mac.
Tugasnya adalah membezakan snapshot yang menangguhkan pembersihan daripada vacuum yang belum dijalankan. Contoh ini menyasarkan PostgreSQL 18 dan menggunakan dua sambungan pangkalan data: satu pembaca yang memegang snapshot Repeatable Read dan satu penulis yang mengemas kini satu artikel. Dokumentasi PostgreSQL pada masa ini mengenal pasti 18 sebagai versi utama yang disokong semasa; butiran pelaksanaan di bawah terikat secara jelas pada cabang 18. Dokumentasi PostgreSQL 18.
Mengapa mengekalkan versi yang tidak dapat dilihat oleh pembaca lama?
Kemas kini menghasilkan versi baris baharu. PostgreSQL mengekalkan versi sebelumnya sementara transaksi lain mungkin masih memerlukannya. Akhirnya, vacuum boleh mendapatkan semula versi lapuk dan menjadikan storannya boleh digunakan semula. Inilah kitar hayat asas MVCC yang diterangkan dalam Pembersihan Rutin.
Perkara yang halus ialah keterlihatan snapshot dan kebolehalihan fizikal menjawab soalan yang berbeza. Keterlihatan menentukan sama ada pertanyaan tertentu patut mengembalikan sesuatu versi. Pembersihan menggunakan cutoff konservatif dan bukannya membuktikan, bagi setiap versi, snapshot sedia ada yang mana boleh membacanya dengan tepat.
Dalam PostgreSQL 18, HeapTupleSatisfiesVacuum membandingkan transaksi yang menjadikan sesuatu versi lapuk dengan OldestXmin. Pemadaman atau penggantian yang telah dikomit tidak serta-merta menjadikan versi sebelumnya boleh dialih keluar: versi yang transaksi pemadamnya berada pada atau selepas cutoff itu terus diklasifikasikan sebagai baru mati. Peraturan ini didokumenkan secara langsung dalam pelaksanaan keterlihatan tupel rasmi.
Pertimbangkan urutan ilustrasi ini. Setiap kemas kini dikomit secara berasingan dan tiada pembaca lain terlibat:
| Langkah | Tindakan | Perkara yang dilihat pembaca lama | Versi terkini yang telah dikomit |
|---|---|---|---|
| 1 | Masukkan semakan artikel 0 | — | Semakan 0 |
| 2 | Pembaca menetapkan snapshot Repeatable Read | Semakan 0 | Semakan 0 |
| 3 | Penulis mengomit semakan 1 | Semakan 0 | Semakan 1 |
| 4 | Penulis mengomit semakan 2 | Semakan 0 | Semakan 2 |
| 5 | Penulis mengomit semakan 3 | Semakan 0 | Semakan 3 |
Semakan 0 diperlukan secara langsung oleh pembaca. Semakan 1 dan 2 ialah versi perantaraan: pembaca ini tidak dapat melihatnya kerana versi tersebut dicipta selepas snapshotnya. Namun begitu, transaksi yang menggantikannya juga lebih baharu daripada cutoff pembersihan yang dikekalkan. Penerapan peraturan cutoff yang didokumenkan menerangkan sebab versi ini boleh kekal bersama semakan 0. Pengekalannya tidak bermakna pembaca boleh menelusurinya sebagai hasil pertanyaan sejarah. Pelaksanaan keterlihatan tupel PostgreSQL 18.
Horizon ini merangkumi lebih daripada baris yang dipilih oleh pembaca. PostgreSQL 18 mengira horizon yang sesuai untuk hubungan berdasarkan ID transaksi bahagian belakang dan horizon snapshot; data jadual biasa secara umumnya mengambil kira sesi dalam pangkalan data yang sama. Ia tidak menjejaki baris yang dikembalikan oleh SELECT tertentu sahaja. Ini menjelaskan sebab pertanyaan kecil boleh menjejaskan pembersihan di tempat lain dalam pangkalan data itu. Hubungan dikongsi dan jenis hubungan lain mempunyai peraturan yang berbeza, jadi “satu snapshot menghentikan semua vacuum di mana-mana” ialah dakwaan yang terlalu luas. Pelaksanaan horizon PostgreSQL 18.
Transaksi manakah yang sebenarnya mengekalkan snapshot lama?
Repeatable Read menetapkan snapshotnya pada pernyataan pertama yang bukan kawalan transaksi, kemudian menggunakan snapshot itu untuk pertanyaan seterusnya. Mengeluarkan BEGIN sahaja tidak mencukupi untuk demonstrasi ini; pertanyaan artikel awal menetapkan paparan yang perlu dikekalkan. Read Committed, tahap pengasingan lalai PostgreSQL, sebaliknya mengambil snapshot baharu bagi setiap arahan. Pengasingan Transaksi.
Oleh itu, transaksi Repeatable Read yang tidak aktif tetapi telah membuat pertanyaan pada jadual boleh mengekalkan snapshotnya selepas pertanyaan selesai. Pernyataan Read Committed yang berjalan lama juga boleh mengekalkan snapshot semasa pelaksanaannya, tetapi pernyataan berasingan yang telah selesai pada tahap itu tidak berkongsi paparan tetap yang sama. Oleh itu, tempoh transaksi sahaja tidak menentukan snapshot yang dikekalkan. Pengasingan Transaksi.
Transaksi baca sahaja boleh menyumbang kepada horizon tanpa mempunyai ID transaksi peringkat atas sendiri. Pelaksanaan mengambil kira ID transaksi bahagian belakang dan horizon snapshot. Dalam contoh ini, backend_xid bernilai null bersama backend_xmin yang bukan null adalah selaras dengan pembaca yang mengekalkan snapshot. Pelaksanaan horizon PostgreSQL 18.
Hasilkan semula tingkah laku daripada Terminal Mac
Anda memerlukan pelayan PostgreSQL 18 tempatan yang sedang berjalan, alat baris arahannya dan Node.js dengan npm. Node.js 20 atau 24 disenaraikan dalam dokumentasi keserasian node-postgres. Panduan ini mengandaikan tetapan sambungan tempatan sedia ada anda berfungsi. Panduan ini mencipta pangkalan data berasingan dan hanya menggunakan teks artikel sintetik. Pengenalan dan pemasangan node-postgres.
Dalam Terminal, cipta direktori kerja, pasang pemacu dan cipta pangkalan data contoh:
mkdir article-snapshot-lab
cd article-snapshot-lab
npm init -y
npm install pg
createdb snapshot_lab
Simpan program lengkap di bawah sebagai snapshot-demo.cjs. Sambungannya mewarisi tetapan persekitaran PostgreSQL, termasuk PGDATABASE, PGHOST dan PGPORT, seperti yang didokumenkan dalam Menyambung dengan node-postgres. Gunakan tetapan sambungan tempatan yang sama untuk createdb dan program tersebut.
Pembaca mengekalkan semua pernyataan transaksi pada satu klien khusus. node-postgres memerlukan perkara ini: transaksi mesti menggunakan klien yang sama sepanjang masa, jadi menghantar pernyataannya melalui pool.query() tidak sesuai. Klien kedua menjalankan kemas kini dan arahan vacuum secara berasingan. Transaksi node-postgres.
Contoh ini juga mengindekskan revision, yang berubah pada setiap kemas kini. Ini sengaja menghalang kemas kini tersebut daripada menggunakan HOT, supaya demonstrasi lebih mudah ditafsirkan. PostgreSQL mendokumenkan bahawa HOT memerlukan kemas kini membiarkan lajur yang dirujuk oleh indeks biasa tidak berubah, di samping ruang yang mencukupi pada halaman asal. Tupel Heap-Only.
const { Client } = require('pg');
async function main() {
const reader = new Client({
application_name: 'article_snapshot_reader',
connectionTimeoutMillis: 5000
});
const writer = new Client({
application_name: 'article_snapshot_writer',
connectionTimeoutMillis: 5000
});
let readerOpen = false;
writer.on('notice', notice => console.log(notice.message));
try {
await reader.connect();
await writer.connect();
const version = await writer.query('SHOW server_version_num');
const major = Math.floor(Number(version.rows[0].server_version_num) / 10000);
if (major !== 18) throw new Error('This walkthrough targets PostgreSQL 18.');
await writer.query(
'CREATE TABLE public.article_snapshot_demo (' +
'id integer PRIMARY KEY, revision integer NOT NULL, title text NOT NULL)'
);
await writer.query(
'CREATE INDEX article_snapshot_demo_revision_idx ' +
'ON public.article_snapshot_demo (revision)'
);
await writer.query(
'INSERT INTO public.article_snapshot_demo VALUES (1, 0, $1)',
['Article draft']
);
await reader.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
readerOpen = true;
const selectRow = 'SELECT id, revision, title FROM public.article_snapshot_demo';
console.log('Reader before:', (await reader.query(selectRow)).rows);
for (const revision of [1, 2, 3]) {
await writer.query(
'UPDATE public.article_snapshot_demo SET revision = $1, title = $2 WHERE id = 1',
[revision, 'Article revision ' + revision]
);
}
const pid = (await reader.query('SELECT pg_backend_pid() AS pid')).rows[0].pid;
console.log('Reader activity:', (await writer.query(
'SELECT state, backend_xid, backend_xmin FROM pg_stat_activity WHERE pid = $1',
[pid]
)).rows);
console.log('Writer latest:', (await writer.query(selectRow)).rows);
console.log('Reader still:', (await reader.query(selectRow)).rows);
console.log('Vacuum with snapshot open:');
await writer.query('VACUUM (VERBOSE) public.article_snapshot_demo');
await reader.query('COMMIT');
readerOpen = false;
console.log('Vacuum after reader commit:');
await writer.query('VACUUM (VERBOSE) public.article_snapshot_demo');
console.log('Reader fresh:', (await reader.query(selectRow)).rows);
} finally {
if (readerOpen) await reader.query('ROLLBACK').catch(console.error);
await Promise.allSettled([reader.end(), writer.end()]);
}
}
main().catch(error => {
console.error(error.message);
process.exitCode = 1;
});
Jalankannya dengan pangkalan data dipilih secara jelas:
PGDATABASE=snapshot_lab node snapshot-demo.cjs
Sintaks JavaScript program ini telah diperiksa menggunakan Node.js. Urutan pangkalan data telah diteliti berdasarkan dokumentasi yang diperoleh; ia tidak dijalankan pada pelayan PostgreSQL untuk artikel ini.
Cara mentafsir dua proses vacuum
Hasil pertanyaan yang dijangka ialah semakan 0 untuk Reader before, semakan 3 untuk Writer latest, dan semakan 0 sekali lagi untuk Reader still. Selepas pembaca mengomit transaksi, pertanyaan kendirinya yang seterusnya sepatutnya mengembalikan semakan 3. Ini ialah hasil yang dijangka daripada urutan sintetik, bukannya output ujian yang dirakam.
Hasil aktiviti sepatutnya mengenal pasti pembaca sebagai idle in transaction semasa penulis memeriksanya, dengan backend_xmin yang dikekalkan. PostgreSQL mentakrifkan keadaan itu sebagai transaksi yang menunggu arahan lain, dan backend_xmin sebagai horizon semasa bahagian belakang. Sistem Statistik Kumulatif.
Baca laporan vacuum pertama untuk versi lapuk yang masih tidak boleh dialih keluar. Selepas pembaca mengomit transaksi, bandingkan laporan kedua untuk melihat pengalihan keluar versi tersebut. Dalam pangkalan data yang sebaliknya senyap, tiga penggantian yang telah dikomit menyediakan tiga versi lapuk untuk diperiksa. Horizon lain yang dikekalkan atau penyelenggaraan serentak boleh mempengaruhi laporan tepat, jadi gunakan corak sebelum dan selepas dan bukannya menghendaki teks output yang serupa.
VACUUM (VERBOSE) menghasilkan laporan aktiviti yang terperinci. Program ini mencetak notis pelayan melalui peristiwa notice node-postgres yang didokumenkan. Kedua-dua panggilan vacuum dijalankan di luar blok transaksi yang jelas kerana PostgreSQL tidak membenarkan VACUUM di dalamnya. PostgreSQL VACUUM, API Klien node-postgres.
Gunakan panduan keputusan ini apabila hasilnya berbeza daripada jangkaan:
| Pemerhatian | Semakan seterusnya |
|---|---|
| Pembaca melihat semakan 3 sebelum mengomit transaksi | Pastikan Repeatable Read dimulakan dan pertanyaan artikel pertama dijalankan sebelum kemas kini. |
| Vacuum pertama mengekalkan versi lapuk | Bandingkannya dengan proses selepas pembaca mengomit transaksi; pengekalan semasa snapshot terbuka adalah dijangka. |
| Versi masih kekal selepas pembaca mengomit transaksi | Semak sama ada terdapat horizon lain yang dikekalkan; menamatkan pembaca ini hanya menghapuskan sumbangannya. |
| Kiraan statistik jadual kelihatan tidak konsisten | Anggap n_dead_tup sebagai anggaran dan bandingkan laporan vacuum. |
| Fail jadual tidak menjadi lebih kecil | Semak sama ada storan menjadi boleh digunakan semula; pengecilan fail ialah hasil yang berasingan. |
PostgreSQL secara jelas menerangkan n_dead_tup sebagai kiraan anggaran. Masa vacuum yang direkodkan juga membuktikan bahawa penyelenggaraan telah dijalankan, tetapi tidak membuktikan bahawa setiap versi lapuk boleh dialih keluar. Medan ini membantu diagnosis, tetapi kedua-duanya tidak menggantikan pemeriksaan snapshot yang dikekalkan dan laporan vacuum. Sistem Statistik Kumulatif.
Vacuum biasa lazimnya menjadikan ruang boleh digunakan semula di dalam jadual dan bukannya memulangkannya kepada macOS. Halaman hujung yang kosong sepenuhnya ialah pengecualian yang didokumenkan. Oleh itu, saiz fail yang kekal selepas pembersihan tidak membuktikan bahawa pembersihan gagal. Pembersihan Rutin.
Apakah yang patut berubah dalam aplikasi?
Jika beberapa pertanyaan artikel perlu berkongsi paparan pangkalan data yang konsisten, hadkan transaksi kepada pertanyaan tersebut dan tamatkan transaksi apabila pertanyaan selesai. Sebagai kesan reka bentuk aplikasi daripada pengekalan snapshot, lakukan pemprosesan yang tidak berkaitan dan interaksi desktop selepas transaksi itu tamat apabila tugas membenarkannya.
Jika setiap pertanyaan boleh menggunakan paparan terkini yang telah dikomit secara berasingan, Read Committed menyediakan snapshot pada peringkat arahan. Mengubah tahap pengasingan atau mengomit transaksi antara kelompok mengubah semantik konsistensi: pertanyaan kemudian mungkin melihat semakan artikel yang baru dikomit. Pilih pendekatan itu hanya apabila hasil yang berubah-ubah tersebut sesuai untuk tugas pembaca. Pengasingan Transaksi.
Meningkatkan kekerapan vacuum tidak dapat menjadikan sesuatu versi melepasi cutoff kebolehalihan yang tidak berubah. Menamatkan snapshot yang tidak diperlukan menangani sumbangannya terhadap pengekalan. Begitu juga, VACUUM FULL ialah penulisan semula jadual yang memerlukan kunci eksklusif serta ruang tambahan; ia bukan tindak balas rutin untuk contoh ini. Pelaksanaan keterlihatan tupel PostgreSQL 18, PostgreSQL VACUUM.
Skrip ini membiarkan jadual sintetiknya tersedia untuk diperiksa. Skrip ini sengaja gagal jika jadual itu sudah wujud, dan bukannya menggantikan kandungan sedia ada. Selepas memeriksanya, jalankan arahan berikut dalam sesi pertanyaan yang disambungkan ke snapshot_lab jika anda mahu mengalih keluar jadual contoh itu. Jika sesuatu proses gagal di tengah-tengah persediaan, periksa jadual yang sama sebelum mengalih keluarnya dan menjalankannya semula.
DROP TABLE public.article_snapshot_demo;
Diagnosis yang berguna ialah peralihan ini: tetapkan snapshot lama, komit beberapa penggantian, jalankan vacuum, tamatkan snapshot dan jalankan vacuum sekali lagi. Melihat semakan asal kekal boleh dibaca sementara versi perantaraan juga masih menolak pembersihan menjadikan perbezaannya jelas. Peraturan keterlihatan PostgreSQL menentukan hasil yang dikembalikan oleh pertanyaan; horizon pembersihannya menentukan masa storan lapuk boleh digunakan semula.
Sumber
13.2. Pengasingan Transaksi — Kumpulan Pembangunan Global PostgreSQL.
24.1. Pembersihan Rutin — Kumpulan Pembangunan Global PostgreSQL.
VACUUM — Kumpulan Pembangunan Global PostgreSQL.
27.2. Sistem Statistik Kumulatif — Kumpulan Pembangunan Global PostgreSQL.
66.7. Tupel Heap-Only (HOT) — Kumpulan Pembangunan Global PostgreSQL.
heapam_visibility.c, REL_18_STABLE — Projek PostgreSQL.
procarray.c, REL_18_STABLE — Projek PostgreSQL.
Pengenalan — node-postgres.
Menyambung — node-postgres.
Transaksi — node-postgres.
API Klien — node-postgres.