在我們使用Postgres工作的許多事情中,我們使用它作爲某種遠程請求的緩存。我們的模式是:Postgres中兩個DELETE查詢如何死鎖?
CREATE TABLE IF NOT EXISTS cache (
key VARCHAR(256) PRIMARY KEY,
value TEXT NOT NULL,
ttl TIMESTAMP DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_cache_ttl ON cache(ttl);
此表沒有觸發器或外鍵。更新通常是:
INSERT INTO cache (key, value, ttl)
VALUES ('Ethan is testing8393645', '"hi6286166"', sec2ttl(300))
ON CONFLICT (key) DO UPDATE
SET value = '"hi6286166"', ttl = sec2ttl(300);
(凡sec2ttl
被定義爲:)
CREATE OR REPLACE FUNCTION sec2ttl(seconds FLOAT)
RETURNS TIMESTAMP AS $$
BEGIN
IF seconds IS NULL THEN
RETURN NULL;
END IF;
RETURN now() + (seconds || ' SECOND')::INTERVAL;
END;
$$ LANGUAGE plpgsql;
查詢緩存在一個事務中做過這樣的:
BEGIN;
DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;
SELECT value FROM cache WHERE key = 'Ethan is testing6460437';
COMMIT;
有幾件事情不喜歡這個設計 - DELETE
發生在緩存「讀取」,(編輯:ASC是默認值,謝謝wargre!)以及我們使用Postgres作爲緩存的事實。但所有這一切本來是可以接受的,除了我們已經開始讓生產死鎖,這往往是這樣的:cache.ttl
上的索引不是升序,這使得它有用ss,
ERROR: deadlock detected
DETAIL: Process 12750 waits for ShareLock on transaction 632693475; blocked by process 10080.
Process 10080 waits for ShareLock on transaction 632693479; blocked by process 12750.
HINT: See server log for query details.
CONTEXT: while deleting tuple (426,1) in relation "cache"
[SQL: 'DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;']
更徹底地調查日誌表明,交易雙方均執行此操作DELETE
。
至於我可以告訴大家:
- 我的交易是在
READ COMMITTED
隔離模式。 - ShareLock被一個事務抓取,表明它想要改變另一個事務已經發生變化(即鎖定)的行。
- 根據
EXPLAIN
查詢的輸出結果,ShareLocks應該被物理順序的DELETE
事務抓取。 - 死鎖表明兩個查詢以不同的順序鎖定行。
如果這一切都是正確的,那麼某種同時發生的事務已經改變了行的物理順序。我發現UPDATE
可以將一行移動到更早或更晚的物理位置,但在我的應用程序中,UPDATE
總是從DELETE
s中刪除行(因爲它們總是延長行的TTL)。如果這些行以前是按照物理順序排列的,並且您刪除了一個,那麼您仍然保持物理順序。同樣爲DELETE
。我們沒有執行任何VACUUM
或您可能期望對行重新排序的任何其他操作。
基於Avoiding PostgreSQL deadlocks when performing bulk update and delete operations,我試圖DELETE
查詢更改爲:
DELETE FROM cache c
USING (
SELECT key
FROM cache
WHERE ttl IS NOT NULL AND now() > ttl
ORDER BY ttl ASC
FOR UPDATE
) del
WHERE del.key = c.key;
不過,我仍然能夠獲得本地死鎖。那麼一般來說,兩個DELETE
查詢怎麼會死鎖呢?是因爲他們以未定義的順序鎖定,如果是這樣,我如何執行特定的訂單?
如何在自動提交和無事務的連接中管理緩存? (順便說一下,索引是正確的,它是由你的dlete使用的) – wargre
我想即使我沒有交易,Postgres圍繞每一個查詢創建一個交易,對吧? (順便說一句,謝謝!我看到ASC是索引的默認值。) – Ethan
「SELECT ... FOR UPDATE」的意義在於爲所有鎖定採集實施全局一致的順序。如果兩個「ttl」值一致,那麼您的行排序是未定義的,並且如果更新了「ttl」值,那麼在明確定義的情況下,排序可能會在併發事務之間有所不同。改爲使用'ORDER BY key'。 –