我認爲這是一個值得深入解答的有趣問題;如果有點冗長,請耐心等待。
簡而言之:你的猜測是正確的,你可以使用下面的RETURNING
條款,以確定是否一行被插入,而不是更新:
RETURNING (xmax = 0) AS inserted
現在詳細的解釋:
當行PostgreSQL不修改數據,但創建該行的新版本;當舊版本不再需要時,舊版本將被自動清除刪除。一行的版本被稱爲元組,所以在PostgreSQL中每行可以有多個元組。
xmax
用於兩個不同的目的:
如文檔中所述,它可以是事務的事務的ID刪除(或更新)的元組(「元組」爲「行另一個字」)。只有交易ID爲xmin
和xmax
之間的交易才能看到元組。如果沒有事務ID小於xmax
的事務,則可以安全地刪除舊元組。
xmax
也用於存儲行鎖。在PostgreSQL中,行鎖並不存儲在鎖表中,而是存儲在元組中以避免鎖表溢出。
如果只有一個事務對行有鎖定,則xmax
將包含鎖定事務的事務ID。如果多個事務在該行上有鎖,則xmax
包含所謂的多重事務的數量,該數據結構又包含鎖定事務的事務ID。
的xmax
文檔是不完整的,因爲這個領域的確切含義被認爲是一個實現細節,不知道元組,這是不立即通過SQL可見t_infomask
不能被理解。
您可以安裝contrib模塊pageinspect
來查看元組的其他字段。
我跑你的例子,這是我看到的時候我用heap_page_items
功能檢查的詳細信息(交易ID號當然是不同的在我的情況):
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)
的t_infomask
含義和t_infomask2
可以在src/include/access/htup_details.h
找到。 lp_off
是頁面中元組數據的偏移量,t_ctid
是當前元組ID,它由頁面內的頁碼和元組號組成。由於表格是新創建的,因此所有數據都在頁面0中。
讓我來討論由heap_page_items
返回的三行。
在線指針(lp
)1,我們發現舊的,更新的元組。它最初有ctid = (0,1)
,但是它被修改爲在更新期間包含當前版本的元組ID。該元組由交易102507創建,並被交易102508(發行INSERT ... ON CONFLICT
的交易)無效。這個元組在VACUUM
期間不會再被看到。
t_infomask
顯示xmin
和xmax
屬於已提交的事務,因此顯示元組的創建和刪除時間。 t_infomask2
顯示該元組更新爲HOT(堆唯一元組)更新,這意味着更新的元組與原始元組位於同一頁面,並且沒有修改索引列(請參閱src/backend/access/heap/README.HOT
)。
在行指針2,我們看到由事務INSERT ... ON CONFLICT
(事務102508)創建的新的更新元組。
t_infomask
表明,該元組更新的結果,xmin
是有效的,並且xmax
包含KEY SHARE
行鎖(這不再是相關的,因爲該交易已完成)。該行鎖在INSERT ... ON CONFLICT
處理期間被採用。 t_infomask2
顯示這是一個HOT元組。
在行指針3我們看到新插入的行。
t_infomask
顯示xmin
有效,xmax
無效。 xmax
設置爲0,因爲此值始終用於新插入的元組。
因此,更新行的非零xmax
是由行鎖造成的實現工件。可以想象,INSERT ... ON CONFLICT
有一天被重新實現,以便這種行爲改變,但我認爲這不太可能。
爲什麼不爲您在更新期間設置的元數據創建另一列? – vol7ron
@ vol7ron因爲它會減慢整個查詢。我覺得現有的專欄(包括系統專欄)就夠了。 – Abelisto
有趣。我的直覺說它應該有效,但它是無證的行爲,並且不能保證這一點在某一天不會改變。我不願意在專業項目中使用它。 – klin