在高并行连接上仅选择一个表行

rab*_*dde 5 mysql parallel-processing mysqli locking message-queue

我正在寻找一种方法为一个线程明确选择一个表行.我编写了一个爬虫程序,它可以处理大约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
Run Code Online (Sandbox Code Playgroud)

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

  • 启动数据库事务
  • 选择喜欢 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
Run Code Online (Sandbox Code Playgroud)

但这些过程似乎导致过高的并行性,因为有时我会在日志中看到两种类型的错误/警告(每隔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)
Run Code Online (Sandbox Code Playgroud)

我的问题是:任何人都可以指出我正确的方向来最小化这些锁定问题吗?(在生产状态下,并行性将比现在高3-4倍,所以我假设会有更多的锁定问题)

编辑2012-12-29:我修改SELECT了使用索引crawler提示USE INDEX(crawler).我现在的问题是lockwait超时了(死锁消失了).

编辑2012-12-31: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
Run Code Online (Sandbox Code Playgroud)

Xno*_*ise 0

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

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

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

编辑: 解决方案

如下所示是正确的。