簡體   English   中英

如何鎖定 MySQL 表的讀/寫,以便我可以 select 然后在沒有其他程序讀/寫數據庫的情況下插入?

[英]How do I lock read/write to MySQL tables so that I can select and then insert without other programs reading/writing to the database?

我正在並行運行許多網絡爬蟲實例。

每個爬蟲從表中選擇一個域,將 url 和一個開始時間插入到日志表中,然后開始對該域進行爬取。

其他並行爬蟲在選擇自己的要爬取的域之前檢查日志表以查看哪些域已經被爬取。

我需要防止其他爬蟲選擇一個剛剛被另一個爬蟲選擇但還沒有日志條目的域。 我對如何做到這一點的最佳猜測是在一個爬蟲選擇一個域並在日志表中插入一行(兩個查詢)時鎖定數據庫以防止所有其他讀/寫操作。

到底是怎么做到的? 恐怕這非常復雜,並且依賴於許多其他事情。 請幫助我開始。


這段代碼似乎是一個很好的解決方案(但是請參閱下面的錯誤):

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT companies.id FROM companies
        LEFT OUTER JOIN crawlLog
        ON companies.id = crawlLog.companyId
        WHERE crawlLog.companyId IS NULL
        LIMIT 1
    ),
    now()
)

但我不斷收到以下 mysql 錯誤:

You can't specify target table 'crawlLog' for update in FROM clause

有沒有辦法在沒有這個問題的情況下完成同樣的事情? 我嘗試了幾種不同的方法。 包括這個:

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT id
        FROM companies
        WHERE id NOT IN (SELECT companyId FROM crawlLog) LIMIT 1
    ),
    now()
)

您可以使用 MySQL LOCK TABLES命令鎖定表,如下所示:

LOCK TABLES tablename WRITE;

# Do other queries here

UNLOCK TABLES;

看:

http://dev.mysql.com/doc/refman/5.5/en/lock-tables.html

您可能不想鎖定表。 如果您這樣做,您將不得不擔心當其他爬蟲嘗試寫入數據庫時捕獲錯誤 - 這就是您在說“......非常復雜並且依賴於許多其他事情”時所想的。

Instead you should probably wrap the group of queries in a MySQL transaction (see http://dev.mysql.com/doc/refman/5.0/en/commit.html ) like this:

START TRANSACTION;
SELECT @URL:=url FROM tablewiththeurls WHERE uncrawled=1 ORDER BY somecriterion LIMIT 1;
INSERT INTO loggingtable SET url=@URL;
COMMIT;

或者類似的東西。

[編輯] 我剛剛意識到 - 您可能可以在一個查詢中完成您需要的所有事情,甚至不必擔心交易。 像這樣的東西:

INSERT INTO loggingtable (url) SELECT url FROM tablewithurls u LEFT JOIN loggingtable l ON l.url=t.url WHERE {some criterion used to pick the url to work on} AND l.url IS NULL.

好吧,表鎖是解決這個問題的一種方法; 但這使得並行請求成為不可能。 如果表是 InnoDB,則可以強制行鎖,在事務中使用SELECT... FOR UPDATE

BEGIN;

SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE

# do whatever you have to do

COMMIT;

請注意,您將需要一個關於domainname (或您在 WHERE 子句中使用的任何列)的索引才能使其正常工作,但這通常是有意義的,我假設您無論如何都會擁有它。

我不會使用鎖定或事務。

go 的最簡單方法是在記錄表中插入一條記錄(如果它尚不存在),然后檢查該記錄。

假設您有tblcrawels (cra_id)填充了爬蟲, tblurl (url_id)填充了 URL,還有一個表tbllogging (log_cra_id, log_url_id)用於您的日志文件。

如果爬蟲 1 想要開始爬取 url 2,您將運行以下查詢:

INSERT INTO tbllogging (log_cra_id, log_url_id) 
SELECT 1, url_id FROM tblurl LEFT JOIN tbllogging on url_id=log_url 
WHERE url_id=2 AND log_url_id IS NULL;

下一步是檢查是否已插入此記錄。

SELECT * FROM tbllogging WHERE log_url_id=2 AND log_cra_id=1

如果你得到任何結果,那么爬蟲 1 可以爬取這個 url。 如果您沒有得到任何結果,這意味着另一個爬蟲已插入同一行並且已經在爬取。

我從@Eljakim 的回答中獲得了一些靈感,並開始了這個新線程,在那里我發現了一個很棒的技巧。 它不涉及鎖定任何東西並且非常簡單。

INSERT INTO crawlLog (companyId, timeStartCrawling)
SELECT id, now()
FROM companies
WHERE id NOT IN
(
    SELECT companyId
    FROM crawlLog AS crawlLogAlias
)
LIMIT 1

最好使用行鎖或基於事務的查詢,以便其他並行請求上下文可以訪問該表。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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