Webクローラーの多くのインスタンスを並行して実行しています。
各クローラーは、テーブルからドメインを選択し、そのURLと開始時刻をログテーブルに挿入して、ドメインのクロールを開始します。
他の並列クローラーは、ログテーブルをチェックして、クロールする独自のドメインを選択する前に、既にクロールされているドメインを確認します。
他のクローラーが別のクローラーによって選択されたばかりで、まだログエントリがないドメインを選択することを防ぐ必要があります。 1つのクローラーがドメインを選択し、ログテーブルに行を挿入する間(2つのクエリ)、他のすべての読み取り/書き込みからデータベースをロックするのがこれを行う方法の最良の推測です。
一体これをどのように行うのでしょうか?これは非常に複雑で、他の多くのものに依存していると思います。始めてください。
このコードは良い解決策のようです(ただし、以下のエラーを参照してください):
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()
)
@Eljakimの回答からインスピレーションを得て、開始しました この新しいスレッド ここで素晴らしいトリックを見つけました。何もロックする必要はなく、非常に簡単です。
INSERT INTO crawlLog (companyId, timeStartCrawling)
SELECT id, now()
FROM companies
WHERE id NOT IN
(
SELECT companyId
FROM crawlLog AS crawlLogAlias
)
LIMIT 1
次のようにMySQL LOCK TABLES
コマンドを使用してテーブルをロックできます。
LOCK TABLES tablename WRITE;
# Do other queries here
UNLOCK TABLES;
見る:
おそらくテーブルをロックしたくないでしょう。その場合、他のクローラーがデータベースに書き込もうとするときにエラーをトラップすることを心配する必要があります。これは、「...非常に複雑で、他の多くのものに依存しています」と言ったときに考えていたものです。
代わりに、おそらく次のようにMySQLトランザクション( http://dev.mysql.com/doc/refman/5.0/en/commit.html を参照)でクエリのグループをラップする必要があります。
START TRANSACTION;
SELECT @URL:=url FROM tablewiththeurls WHERE uncrawled=1 ORDER BY somecriterion LIMIT 1;
INSERT INTO loggingtable SET url=@URL;
COMMIT;
またはそれに近いもの。
[編集]気付いた-おそらく、1つのクエリで必要なすべてを実行でき、トランザクションについて心配する必要さえありません。このようなもの:
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.
さて、テーブルロックはそれに対処する1つの方法です。しかし、これにより並列リクエストが不可能になります。テーブルがInnoDBの場合、代わりにトランザクション内で SELECT ... FOR UPDATE を使用して、行ロックを強制できます。
BEGIN;
SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE
# do whatever you have to do
COMMIT;
これが機能するためには、domainname
(またはWHERE節で使用する列)のインデックスが必要になることに注意してください。
ロックやトランザクションは使用しません。
最も簡単な方法は、ログテーブルにレコードがまだ存在しない場合に挿入し、そのレコードを確認することです。
クローラーで満たされたtblcrawels (cra_id)
と、URLで満たされたtblurl (url_id)
、およびログファイル用のテーブル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をクロールできます。結果が得られない場合、これは別のクローラーが同じ行に挿入され、既にクロールされていることを意味します。