我正在并行运行一个webcrawler的许多实例。
每个抓取工具从表中选择一个域,将该网址和开始时间插入到日志表中,然后开始抓取该域。
在选择自己要抓取的域之前,其他并行抓取工具会检查日志表以查看哪些域已被抓取。
我需要阻止其他抓取工具选择刚刚被其他抓取工具选中但尚未拥有日志条目的域。我最好的猜测是如何锁定数据库以防止所有其他读/写,同时一个爬虫选择一个域并在日志表中插入一行(两个查询)。
如何做到这一点?我担心这非常复杂,并且依赖于许多其他事情。请帮助我开始。
此代码似乎是一个很好的解决方案(但请参阅下面的错误):
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()
)
答案 0 :(得分:45)
您可以使用MySQL LOCK TABLES
命令锁定表,如下所示:
LOCK TABLES tablename WRITE;
# Do other queries here
UNLOCK TABLES;
请参阅:
答案 1 :(得分:4)
您可能不想锁定表格。如果你这样做,你将不得不担心当其他爬虫尝试写入数据库时陷阱错误 - 当你说“......非常复杂并且依赖于许多其他事情时,你会想到这一点。”
相反,您应该将这组查询包装在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;
或者接近那个。
[edit]我刚刚意识到 - 你可以在一个查询中完成所需的一切,甚至不必担心交易。像这样:
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.
答案 2 :(得分:3)
我不会使用锁定或交易。
最简单的方法是在记录表中插入记录(如果它还不存在),然后检查该记录。
假设您的抓取工具tblcrawels (cra_id)
已填充,tblurl (url_id)
填充了网址,并且表tbllogging (log_cra_id, log_url_id)
为您的日志文件。
如果抓取工具1想要开始抓取网址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可以抓取此网址。如果您没有得到任何结果,这意味着另一个爬虫已插入同一行并且已经爬行。
答案 3 :(得分:2)
嗯,表锁是解决这个问题的一种方法;但这使得并行请求变得不可能。如果表是InnoDB,则可以在事务中使用SELECT ... FOR UPDATE强制执行行锁定。
BEGIN;
SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE
# do whatever you have to do
COMMIT;
请注意,您需要domainname
上的索引(或者您在WHERE子句中使用的任何列)才能使用此索引,但这一般是有道理的,我认为无论如何都会有。< / p>
答案 4 :(得分:2)
我从@ Eljakim的回答中得到了一些灵感,并开始this new thread,我发现了一个很棒的伎俩。它不涉及锁定任何东西,非常简单。
INSERT INTO crawlLog (companyId, timeStartCrawling)
SELECT id, now()
FROM companies
WHERE id NOT IN
(
SELECT companyId
FROM crawlLog AS crawlLogAlias
)
LIMIT 1