【问题标题】: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?如何锁定对 MySQL 表的读/写,以便我可以选择然后插入,而无需其他程序读/写数据库?
【发布时间】:2011-10-01 01:37:33
【问题描述】:

我正在并行运行多个网络爬虫实例。

每个爬虫从表中选择一个域,将该 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 locking web-crawler mysql-error-1093


    【解决方案1】:

    我不会使用锁定或事务。

    最简单的方法是在记录表中插入一条记录(如果它尚不存在),然后检查该记录。

    假设您的 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 可以爬取此网址。如果您没有得到任何结果,这意味着另一个爬虫已插入同一行并且已经在爬取。

    【讨论】:

    • 我在尝试执行您的建议时不断收到此错误:“您无法在 FROM 子句中指定目标表 'crawlLog' 进行更新”——请参阅我的代码更新问题。
    • 我遗漏了您建议的部分内容,因为我不太确定您的部分代码。 1 和 2 在那里做什么?
    • 1是爬虫的id,2是url的id。它们来自表 tblcrawlers 和 tblurl。
    【解决方案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
    

    【讨论】:

    • 然后你执行SELECT LAST_INSERT_ID(); 来获取日志条目的 ID,其中包含爬虫应抓取的域,不是吗?
    • 我也不认为这是可靠的。我有一个多线程应用程序,类似地试图在 cron 计时器上插入独特的作业。如果它们的发射距离太近(例如低至 3 位微秒),尽管进行了 WHERE 检查,它们仍会插入。
    • @Rikaelus 我有同样的问题,在多线程应用程序中,尽管有“where not in”子句,插入仍然发生。您是如何解决问题的?
    • @PramodRoy 很难在评论中解释,但这是一些 DBA-fu。我插入一个可重复的标识符和一个设置为 1 的“声明”列。这两列是唯一键。我等待 5 秒,然后将“声明”列设置为 NULL,然后执行此工作。任何其他尝试使用相同标识符并声明 = 1 的线程/进程都会引发 IntegrityError 异常(重复键),我会捕获并中止该异常。它利用了在唯一键中处理 NULL 的方式。这可能不适用于“等待”时间不可行的应用程序。
    • @Rikaelus 这听起来像是对架构的一些更改。然而,对我来说,由于我的应用程序不是数据密集型的,因此获取表上的锁是我唯一的解决方案。这对我来说并不会显着降低吞吐量,因为我只在特定场景下获取表锁,并且只针对数据库中的一个表。
    【解决方案3】:

    好吧,表锁是解决这个问题的一种方法;但这使得并行请求成为不可能。如果表是 InnoDB,则可以强制行锁,在事务中使用 SELECT ... FOR UPDATE

    BEGIN;
    
    SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE
    
    # do whatever you have to do
    
    COMMIT;
    

    请注意,您需要在 domainname(或您在 WHERE 子句中使用的任何列)上建立一个索引才能使其正常工作,但这通常是有道理的,我假设您无论如何都会拥有它。

    【讨论】:

    • 我参加聚会有点晚了,但是如果我没有任何更新查询怎么办?什么时候会为此释放锁定? @wonk0
    【解决方案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.
    

    【讨论】:

    • 我看不出这有什么帮助;什么会阻止两个并行实例读取相同的 url?
    • 你是对的 - 它不会。请参阅我刚刚编辑的第二个示例。它在 URL 表上加入日志记录表,确保日志记录表中没有记录(l.url IS NULL)。
    • 是的,这样更好。但是必须记住,这绝对不会在事务中起作用(取决于隔离级别)
    【解决方案5】:

    您可以像这样使用 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

    【讨论】:

    • 当其他进程在锁期间试图读取或写入表时,会得到一个mysql错误。
    • @AlbertHendriks 来自文档:“在持有 WRITE 锁时,其他会话对表的锁定请求会阻塞。”
    • 是的,所以如果他们只是尝试查询,他们会得到一个错误,但如果他们尝试获取锁,它会等待。
    • @AlbertHendriks 针对锁定表的查询不会因错误而被拒绝(除非我认为连接与 Web 服务或其他东西相关联并超时)。锁只是暂停任何其他想要访问该表的连接,直到锁被移除。一旦表被解锁,连接将自动恢复访问表。
    • @DavidMordigal,任何针对锁定表运行的插入查询会在表解锁时自动执行吗?
    猜你喜欢
    • 2012-07-29
    • 2019-08-21
    • 1970-01-01
    • 2011-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-16
    • 2011-12-18
    相关资源
    最近更新 更多