【问题标题】:Select only one table row on high parallel connections在高并行连接上仅选择一个表格行
【发布时间】:2012-12-23 20:45:18
【问题描述】:

我正在寻找一种方法来为一个线程显式选择一个表行。我写了一个爬虫,它可以处理大约 50 个并行进程。每个进程都必须从表中取出一行并进行处理。

CREATE TABLE `crawler_queue` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `url` text NOT NULL,
 `class_id` tinyint(3) unsigned NOT NULL,
 `server_id` tinyint(3) unsigned NOT NULL,
 `proc_id` mediumint(8) unsigned NOT NULL,
 `prio` tinyint(3) unsigned NOT NULL,
 `inserted` int(10) unsigned NOT NULL,
 PRIMARY KEY (`id`),
 KEY `proc_id` (`proc_id`),
 KEY `app_id` (`app_id`),
 KEY `crawler` (`class_id`,`prio`,`proc_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

现在我的流程执行以下操作:

  • 启动数据库事务
  • 选择SELECT * FROM crawler_queue WHERE class_id=2 AND prio=20 AND proc_id=0 ORDER BY id LIMIT 1 FOR UPDATE
  • 然后用UPDATE crawler_queue SET server_id=1,proc_id=1376 WHERE id=23892 更新这一行
  • 提交交易

这应该有助于没有其他进程可以获取尚未处理的行。对精选节目进行解释

id  select_type  table          type  possible_keys    key      key_len  ref    rows    Extra
1   SIMPLE       crawler_queue  ref   proc_id,crawler  proc_id  3        const  617609  Using where

但这些进程似乎会导致并行度过高,因为有时我会在日志中看到两种类型的错误/警告(每 5 分钟左右):

mysqli::query(): (HY000/1205): Lock wait timeout exceeded; try restarting transaction (in /var/www/db.php l
ine 81)

mysqli::query(): (40001/1213): Deadlock found when trying to get lock; try restarting transaction (in /var/www/db.php line 81)

我的问题是:谁能指出我正确的方向以尽量减少这些锁定问题? (在生产状态下,并行度会比现在高 3-4 倍,所以我假设会有更多的锁定问题)

我通过提示 USE INDEX(crawler) 修改了 SELECT 以使用索引 crawler。我现在的问题是 lockwait 超时了(死锁消失了)。

EXPLAINUSE INDEX() 现在显示(行数更高,因为表现在包含更多数据):

id  select_type  table          type  possible_keys    key      key_len  ref                rows     Extra
1   SIMPLE       crawler_queue  ref   proc_id,crawler  crawler  5        const,const,const  5472426  Using where

【问题讨论】:

    标签: mysql mysqli parallel-processing locking message-queue


    【解决方案1】:

    您的 EXPLAIN 报告显示您仅使用单列索引 proc_id,并且查询必须检查超过 600K 行。如果优化器选择crawler 索引可能会更好。

    InnoDB 可能会锁定所有 600K 行,而不仅仅是与 WHERE 子句中的完整条件匹配的行。 InnoDB 锁定所有检查的行,以确保并发更改不会以错误的顺序写入 binlog。

    解决方案是使用索引来缩小检查行的范围。这可能不仅可以帮助您更快地找到行,还可以避免锁定大范围的行。 crawler 索引在这里应该有所帮助,但目前尚不清楚它为什么不使用该索引。

    您可能需要ANALYZE TABLE 以确保更新 InnoDB 的表统计信息以了解 crawler 索引,然后才能在优化计划中使用该索引。 ANALYZE TABLE 是一种廉价的操作。

    另一种选择是使用索引提示:

    SELECT * FROM crawler_queue USE INDEX(crawler) ...
    

    这告诉优化器使用该索引,并且不考虑该查询的其他索引。我更喜欢避免索引提示,因为优化器通常能够自己做出正确的决定,并且在代码中使用提示意味着我可能会迫使优化器不考虑我将来创建的索引,否则它会选择.


    有了更多解释,现在很清楚您将 RDBMS 用作 FIFO。这不是对 RDBMS 的有效使用。有用于此目的的消息队列技术。

    另见:

    【讨论】:

    • 嘿,比尔,这就是我已经完成的工作(抱歉没有更新我的问题,不过会给 +1)。但奇怪的是,这个解释有时会显示crawler 的用法,而不是proc_id。但现在我强制使用索引crawler。我也会尝试分析表命令。谢谢
    • 检查 EXPLAIN 输出中的 rows 字段。我希望使用复合索引,检查的行数应该更少。
    • 不,它没有(见上文)
    • 强制使用主键怎么办?该表的工作方式类似于 FIFO,因此该进程会查找第一个空闲行。在大多数情况下,前 400-500 行已经被其他线程处理,所以 USE INDEX(PRIMARY) 应该只请求几百行,不是吗?
    【解决方案2】:

    据我所知,您面临的问题是两个线程正在争夺表中的同一行,并且它们都不能拥有它。但是数据库没有任何优雅的方式可以说“不,你不能拥有那个,找到另一行”,因此你会得到错误。这称为资源争用。

    当您进行这样的高度并行工作时,减少基于争用的问题的最简单方法之一是通过发明一种方法让所有线程知道它们应该提前处理哪些行来完全消除争用的时间。然后他们可以锁定而不必争用资源,并且您的数据库不必解决争用问题。

    如何最好地做到这一点?通常人们会选择某种线程 ID 方案并使用模算术来确定哪些线程获取哪些行。如果你有 10 个线程,那么线程 0 将获得第 0、10、20、30 等行。线程 1 获得 1、11、21、31 等。

    一般来说,如果您有 NUM_THREADS 个,那么您的每个线程都会从数据库中选择 THREAD_ID + i*NUM_THREADS 个 ID 并处理这些 ID。

    我们引入了一个问题,即线程可能会停止或死亡,您最终可能会在数据库中得到永远不会被触及的行。该问题有几种解决方案,其中之一是在大多数/所有线程完成后运行“清理”,其中所有线程零碎地抓取它们可以抓取的任何行并抓取它们,直到没有未抓取的 URL 离开。你可以变得更复杂,让一些清理线程不断运行,或者让每个线程偶尔执行清理任务,等等。

    【讨论】:

      【解决方案3】:

      更好的解决方案是进行更新并完全跳过选择。然后您可以使用last_insert_id() 获取更新的项目。这应该允许您完全跳过锁定,同时执行更新。一旦记录更新,您就可以开始处理它,因为它永远不会被完全相同的查询再次选择,考虑到并非所有初始条件都匹配了。

      我认为这应该可以帮助您减轻与锁定相关的所有问题,并且应该允许您并行运行任意数量的进程。

      PS:澄清一下,我说的是update ... limit 1,以确保您只更新一行。

      编辑: Solution

      是正确的,如下所示。

      【讨论】:

      • 好主意,但 LAST_INSERT_ID() 仅当您 INSERT 数据或 UPDATE 增加自动增量列时才会返回值:编辑我会给 @987654322 @一试
      • 由于某种原因,我在测试时得到了 last_insert_id 值,但它欺骗了我(它看起来像是正确的,但事实并非如此)。我相信该 SO 问题中描述的解决方案是可行的方法。我也会更新我的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 2015-08-14
      • 1970-01-01
      • 1970-01-01
      • 2018-09-13
      • 2012-06-28
      相关资源
      最近更新 更多