Saya pikir ini adalah pertanyaan menarik yang layak mendapatkan jawaban mendalam; mohon bersabar jika agak panjang.
Singkatnya:Tebakan Anda benar, dan Anda dapat menggunakan RETURNING
berikut ini klausa untuk menentukan apakah baris dimasukkan dan tidak diperbarui:
RETURNING (xmax = 0) AS inserted
Sekarang penjelasan detailnya:
Saat baris diperbarui, PostgreSQL tidak mengubah data, tetapi membuat versi baru dari baris; versi lama akan dihapus oleh autovacuum ketika sudah tidak dibutuhkan lagi. Versi baris disebut tuple , jadi di PostgreSQL bisa ada lebih dari satu tupel per baris.
xmax
melayani dua tujuan yang berbeda:
-
Sebagaimana dinyatakan dalam dokumentasi, itu dapat berupa ID transaksi dari transaksi yang menghapus (atau memperbarui) tuple (“tuple” adalah kata lain untuk “baris”). Hanya transaksi dengan ID transaksi antara
xmin
danxmax
dapat melihat tupel. Tuple lama dapat dihapus dengan aman jika tidak ada transaksi dengan ID transaksi kurang darixmax
. -
xmax
juga digunakan untuk menyimpan kunci baris . Di PostgreSQL, kunci baris tidak disimpan di tabel kunci, tetapi di tupel untuk menghindari meluapnya tabel kunci.
Jika hanya satu transaksi yang memiliki kunci di baris,xmax
akan berisi ID transaksi dari transaksi penguncian. Jika lebih dari satu transaksi memiliki kunci pada baris,xmax
berisi jumlah yang disebut multixact , yang merupakan struktur data yang pada gilirannya berisi ID transaksi dari transaksi penguncian.
Dokumentasi xmax
tidak lengkap, karena arti sebenarnya dari bidang ini dianggap sebagai detail implementasi dan tidak dapat dipahami tanpa mengetahui t_infomask
dari tuple, yang tidak langsung terlihat melalui SQL.
Anda dapat menginstal modul contrib pageinspect
untuk melihat ini dan bidang lain dari sebuah tuple.
Saya menjalankan contoh Anda, dan inilah yang saya lihat ketika saya menggunakan heap_page_items
fungsi untuk memeriksa detail (nomor ID transaksi tentu saja berbeda dalam kasus saya):
SELECT *, ctid, xmin, xmax FROM t;
┌───┬────┬───────┬────────┬────────┐
│ i │ x │ ctid │ xmin │ xmax │
├───┼────┼───────┼────────┼────────┤
│ 1 │ 11 │ (0,2) │ 102508 │ 102508 │
│ 2 │ 22 │ (0,3) │ 102508 │ 0 │
└───┴────┴───────┴────────┴────────┘
(2 rows)
SELECT lp, lp_off, t_xmin, t_xmax, t_ctid,
to_hex(t_infomask) AS t_infomask, to_hex(t_infomask2) AS t_infomask2
FROM heap_page_items(get_raw_page('laurenz.t', 0));
┌────┬────────┬────────┬────────┬────────┬────────────┬─────────────┐
│ lp │ lp_off │ t_xmin │ t_xmax │ t_ctid │ t_infomask │ t_infomask2 │
├────┼────────┼────────┼────────┼────────┼────────────┼─────────────┤
│ 1 │ 8160 │ 102507 │ 102508 │ (0,2) │ 500 │ 4002 │
│ 2 │ 8128 │ 102508 │ 102508 │ (0,2) │ 2190 │ 8002 │
│ 3 │ 8096 │ 102508 │ 0 │ (0,3) │ 900 │ 2 │
└────┴────────┴────────┴────────┴────────┴────────────┴─────────────┘
(3 rows)
Arti dari t_infomask
dan t_infomask2
dapat ditemukan di src/include/access/htup_details.h
. lp_off
adalah offset dari data Tuple di halaman, dan t_ctid
adalah ID tupel saat ini yang terdiri dari nomor halaman dan nomor tuple di dalam halaman. Karena tabel baru dibuat, semua data ada di halaman 0.
Biarkan saya membahas tiga baris yang dikembalikan oleh heap_page_items
.
-
Di penunjuk garis (
lp
) 1 kami menemukan tuple lama yang diperbarui. Awalnya memilikictid = (0,1)
, tetapi itu dimodifikasi untuk memuat ID Tuple dari versi saat ini selama pembaruan. Tuple dibuat oleh transaksi 102507 dan dibatalkan oleh transaksi 102508 (transaksi yang mengeluarkanINSERT ... ON CONFLICT
). Tuple ini tidak terlihat lagi dan akan dihapus selamaVACUUM
.t_infomask
menunjukkan bahwa keduanyaxmin
danxmax
milik transaksi yang berkomitmen dan akibatnya menunjukkan kapan tupel dibuat dan dihapus.t_infomask2
menunjukkan bahwa tupel telah diperbarui dengan HOT (heap only Tuple ) update, yang berarti bahwa tupel yang diperbarui berada di halaman yang sama dengan tupel asli dan tidak ada kolom terindeks yang diubah (lihatsrc/backend/access/heap/README.HOT
). -
Pada penunjuk baris 2 kita melihat tupel baru yang diperbarui yang dibuat oleh transaksi
INSERT ... ON CONFLICT
(transaksi 102508).t_infomask
menunjukkan bahwa tuple ini adalah hasil pembaruan,xmin
valid, danxmax
berisiKEY SHARE
kunci baris (yang tidak lagi relevan sejak transaksi selesai). Kunci baris ini diambil selamaINSERT ... ON CONFLICT
pengolahan.t_infomask2
menunjukkan bahwa ini adalah tupel PANAS. -
Pada penunjuk baris 3 kita melihat baris yang baru dimasukkan.
t_infomask
menunjukkan bahwaxmin
valid danxmax
tidak valid.xmax
disetel ke 0 karena nilai ini selalu digunakan untuk tupel yang baru dimasukkan.
Jadi xmax
bukan nol dari baris yang diperbarui adalah artefak implementasi yang disebabkan oleh kunci baris. Bisa dibayangkan bahwa INSERT ... ON CONFLICT
diterapkan kembali suatu hari sehingga perilaku ini berubah, tetapi saya pikir itu tidak mungkin.