[英]PostgreSQL concurrent transaction issues
我正在構建一個爬蟲。 多個爬網工作者訪問同一個PostgreSQL數據庫。 可悲的是,我遇到了這里提出的主要交易的問題:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE webpages
SET locked = TRUE
WHERE url IN
(
SELECT DISTINCT ON (source) url
FROM webpages
WHERE
(
last IS NULL
OR
last < refreshFrequency
)
AND
locked = FALSE
LIMIT limit
)
RETURNING *;
COMMIT;
url
是一個URL(String) source
是域名(String) last
是抓取頁面的最后一次(日期) locked
是一個布爾值,設置為表示當前正在抓取網頁(布爾值) 我嘗試了兩種不同的事務隔離級別:
ISOLATION LEVEL SERIALIZABLE
,我得到錯誤, could not serialize access due to concurrent update
ISOLATION LEVEL READ COMMITTED
,我從並發事務中獲取重復的url
,因為數據從事務首次提交時被“凍結”(我認為) 我對PostgreSQL和SQL很新,所以我真的不確定我能做些什么來解決這個問題。
更新:
PostgreSQL版本是9.2.x.
webpage
表定義:
CREATE TABLE webpages (
last timestamp with time zone,
locked boolean DEFAULT false,
url text NOT NULL,
source character varying(255) PRIMARY KEY
);
這個問題留下了解釋的空間。 這就是我理解任務的方式:
鎖定最多符合某些條件且尚未鎖定的limit
URL。 為了分散源上的負載,每個URL應來自不同的源。
假設一個單獨的表source
:這使得工作更快更容易。 如果你沒有這樣的表,創建它,無論如何它是正確的設計:
CREATE TABLE source (
source_id serial NOT NULL PRIMARY KEY
, source text NOT NULL
);
CREATE TABLE webpage (
source_id int NOT NULL REFERENCES source
url text NOT NULL PRIMARY KEY
locked boolean NOT NULL DEFAULT false, -- may not be needed
last timestamp NOT NULL DEFAULT '-infinity' -- makes query simpler
);
或者,您可以有效地使用遞歸CTE:
即使在默認的read committed
隔離級別,我正在使用建議鎖來使這個安全且便宜:
UPDATE webpage w
SET locked = TRUE
FROM (
SELECT (SELECT url
FROM webpage
WHERE source_id = s.source_id
AND (last >= refreshFrequency) IS NOT TRUE
AND locked = FALSE
AND pg_try_advisory_xact_lock(url) -- only true is free
LIMIT 1 -- get 1 URL per source
) AS url
FROM (
SELECT source_id -- the FK column in webpage
FROM source
ORDER BY random()
LIMIT limit -- random selection of "limit" sources
) s
FOR UPDATE
) l
WHERE w.url = l.url
RETURNING *;
或者,您可以僅使用咨詢鎖,而不使用已locked
的表列。 基本上只需運行SELECT
語句。 鎖保留到交易結束。 您可以使用pg_try_advisory_lock()
來保持鎖定直到會話結束。 只有UPDATE
一次設置last
完成時(並可能釋放的咨詢鎖)。
在Postgres 9.3或更高版本中,您將使用LATERAL
連接而不是相關子查詢。
我選擇了pg_try_advisory_xact_lock()
因為鎖可以(並且應該)在事務結束時釋放。 咨詢鎖的詳細說明:
如果某些源沒有要爬網的URL,則會獲得少於行的limit
。
隨機選擇的來源是我瘋狂但有根據的猜測,因為沒有信息。 如果你的source
表很大,有更快的方法:
refreshFrequency
應該被稱為像lastest_last
,因為它不是一個“頻率”,而是一個timestamp
或date
。
要獲得完整限制行數( 如果可用) ,請使用RECURSIVE
CTE並迭代所有源,直到找到足夠的或不能找到更多。
正如我上面提到的,您可能根本不需要locked
列,只使用咨詢鎖(更便宜)。 在開始下一輪之前,只需在交易結束時設置last
一個。
WITH RECURSIVE s AS (
SELECT source_id, row_number() OVER (ORDER BY random()) AS rn
FROM source -- you might exclude "empty" sources early ...
)
, page(source_id, rn, ct, url) AS (
SELECT 0, 0, 0, ''::text -- dummy init row
UNION ALL
SELECT s.source_id, s.rn
, CASE WHEN t.url <> ''
THEN p.ct + 1
ELSE p.ct END -- only inc. if url found last round
, (SELECT url
FROM webpage
WHERE source_id = t.source_id
AND (last >= refreshFrequency) IS NOT TRUE
AND locked = FALSE -- may not be needed
AND pg_try_advisory_xact_lock(url) -- only true is free
LIMIT 1 -- get 1 URL per source
) AS url -- try, may come up empty
FROM page p
JOIN s ON s.rn = p.rn + 1
WHERE CASE WHEN p.url <> ''
THEN p.ct + 1
ELSE p.ct END < limit -- your limit here
)
SELECT url
FROM page
WHERE url <> ''; -- exclude '' and NULL
或者,如果您還需要管理locked
,請將此查詢與上述UPDATE
。
你會喜歡即將推出的Postgres 9.5中的SKIP LOCKED
:
有關:
第一次嘗試:
UPDATE webpages
SET locked = TRUE
WHERE url IN
(
SELECT DISTINCT ON (source) url
FROM webpages
WHERE
(
last IS NULL
OR
last < refreshFrequency
)
AND
locked = FALSE
LIMIT limit
)
WHERE
(
last IS NULL
OR
last < refreshFrequency
)
AND
locked = FALSE
您正嘗試僅更新locked = FALSE
記錄。
想象一下,表中有以下記錄:
URL locked
----------------
A false
A true
更新中的子查詢將重新啟動A
然后外部更新將執行:
UPDATE webpages
SET locked = TRUE
WHERE url IN ( 'A' )
實際上,包含url = A
的表中的所有記錄都將被更新,
locked
列中的值的重新定義。
您需要向外部更新應用與子查詢中相同的WHERE
條件。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.