2016-08-20 41 views
11

聲明:理論問題。PostgreSQL Upsert使用系統列XMIN,XMAX和其他方法區分插入和更新的行

有人問這裏有幾個問題,關於如何區分PostgreSQL upsert語句中插入和更新的行。

下面是一個簡單的例子:

[email protected]=# create table t(i int primary key, x int); 
[email protected]=# insert into t values(1,1); 
[email protected]=# insert into t values(1,11),(2,22) 
    on conflict(i) do update set x = excluded.i*11 
    returning *, xmin, xmax; 
╔═══╤════╤══════╤══════╗ 
║ i │ x │ xmin │ xmax ║ 
╠═══╪════╪══════╪══════╣ 
║ 1 │ 11 │ 7696 │ 7696 ║ 
║ 2 │ 22 │ 7696 │ 0 ║ 
╚═══╧════╧══════╧══════╝ 

所以,xmax> 0(或xmax = xmin) - 行被更新; xmax = 0 - 插入行。

IMO不清楚解釋xminxmaxhere的含義。

是否可以將邏輯基於這些列?是否有關於系統列的更多重要解釋(源代碼除外)?

最後是我的猜測正確的更新/插入行?

+0

爲什麼不爲您在更新期間設置的元數據創建另一列? – vol7ron

+0

@ vol7ron因爲它會減慢整個查詢。我覺得現有的專欄(包括系統專欄)就夠了。 – Abelisto

+0

有趣。我的直覺說它應該有效,但它是無證的行爲,並且不能保證這一點在某一天不會改變。我不願意在專業項目中使用它。 – klin

回答

16

我認爲這是一個值得深入解答的有趣問題;如果有點冗長,請耐心等待。

簡而言之:你的猜測是正確的,你可以使用下面的RETURNING條款,以確定是否一行被插入,而不是更新:

RETURNING (xmax = 0) AS inserted 

現在詳細的解釋:

當行PostgreSQL不修改數據,但創建該行的新版本;當舊版本不再需要時,舊版本將被自動清除刪除。一行的版本被稱爲元組,所以在PostgreSQL中每行可以有多個元組。

xmax用於兩個不同的目的:

  1. 如文檔中所述,它可以是事務的事務的ID刪除(或更新)的元組(「元組」爲「行另一個字」)。只有交易ID爲xminxmax之間的交易才能看到元組。如果沒有事務ID小於xmax的事務,則可以安全地刪除舊元組。

  2. 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返回的三行。

  1. 線指針lp)1,我們發現舊的,更新的元組。它最初有ctid = (0,1),但是它被修改爲在更新期間包含當前版本的元組ID。該元組由交易102507創建,並被交易102508(發行INSERT ... ON CONFLICT的交易)無效。這個元組在VACUUM期間不會再被看到。

    t_infomask顯示xminxmax屬於已提交的事務,因此顯示元組的創建和刪除時間。 t_infomask2顯示該元組更新爲HOT(堆唯一元組)更新,這意味着更新的元組與原始元組位於同一頁面,並且沒有修改索引列(請參閱src/backend/access/heap/README.HOT)。

  2. 在行指針2,我們看到由事務INSERT ... ON CONFLICT(事務102508)創建的新的更新元組。

    t_infomask表明,該元組更新的結果,xmin是有效的,並且xmax包含KEY SHARE行鎖(這不再是相關的,因爲該交易已完成)。該行鎖在INSERT ... ON CONFLICT處理期間被採用。 t_infomask2顯示這是一個HOT元組。

  3. 在行指針3我們看到新插入的行。

    t_infomask顯示xmin有效,xmax無效。 xmax設置爲0,因爲此值始終用於新插入的元組。

因此,更新行的非零xmax是由行鎖造成的實現工件。可以想象,INSERT ... ON CONFLICT有一天被重新實現,以便這種行爲改變,但我認爲這不太可能。

+0

感謝您對系統欄的詳細解釋。瞭解它如何在內部工作是非常有用的。 – Abelisto

+2

這是一個優秀且有趣的答案,值得讚賞。 –