簡體   English   中英

PostgreSQL並發事務問題

[英]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 ,因為它不是一個“頻率”,而是一個timestampdate

遞歸交替

要獲得完整限制行數( 如果可用) ,請使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM