我目前正在构建一个抓取工具。多个爬网工作者访问同一个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(字符串)source
是域名(String)last
是抓取页面的最后一次(日期)locked
是一个布尔值,设置为表示当前正在抓取网页(布尔值)我尝试了两种不同的事务隔离级别:
ISOLATION LEVEL SERIALIZABLE
,我收到could not serialize access due to concurrent update
ISOLATION LEVEL READ COMMITTED
,我从并发交易中得到重复的url
,因为数据已被冻结&#34;从交易首次提交时起(我认为)我对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
);
答案 0 :(得分:3)
这个问题留有解释空间。这就是我理解任务的方式:
锁定最多符合某些条件并且尚未锁定的limit
个网址。要在源上分散负载,每个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
隔离级别,我使用advisory locks也可以保证安全且便宜:
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()
,因为锁可以(并且应该)在事务结束时释放。咨询锁的详细说明:
如果某些来源没有要抓取的网址,则小于limit
行。
由于信息不可用,随机选择的来源是我疯狂但有根据的猜测。如果您的source
表很大,则有更快的方法:
refreshFrequency
应该被称为lastest_last
,因为它不是&#34;频率&#34;,而是timestamp
或{{ 1}}。
要获取完整的限制行(如果可用),请使用date
CTE并迭代所有来源,直到找到足够的或者找不到更多来源
正如我上面提到的,您可能根本不需要列RECURSIVE
,只使用咨询锁(更便宜)。只需在交易结束时设置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
一起使用。
在即将到来的 Postgres 9.5 中,您会喜欢 UPDATE
:
相关:
答案 1 :(得分:0)
首先尝试:
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
条件。