MySQL - 如何有效地获取ID最低的行?

时间:2010-09-08 11:27:32

标签: mysql message-queue database-performance

是否有更快的方法来更新匹配特定条件的MySQL表的最旧行,而不是像以下查询中那样使用ORDER BY id LIMIT 1

UPDATE mytable SET field1 = '1' WHERE field1 = 0 ORDER BY id LIMIT 1;

注意:

  • 假设主键为idfield1上还有一个索引。
  • 我们正在更新单行
  • 我们没有严格更新最早的行,我们正在更新符合条件的最旧行
  • 我们想要更新最旧的匹配行,即最低id,即FIFO队列的头部。

问题:

  • ORDER BY id是否必要? MySQL如何默认订购?

真实世界的例子

我们有一个DB表用于电子邮件队列。当我们要将电子邮件排队以发送给我们的用户时,会添加行。行由cron作业删除,每分钟运行一次,在该分钟内尽可能多地处理并每行发送1封电子邮件。

我们计划放弃此方法并使用GearmanResque之类的方法来处理我们的电子邮件队列。但与此同时,我有一个问题,我们如何有效地标记队列中最旧的项目进行处理,a.k.a。具有最低ID的行。此查询完成了这项工作:

mysql_query("UPDATE email_queue SET processingID = '1' WHERE processingID = 0 ORDER BY id LIMIT 1");

然而,由于扩展问题,它出现在mysql慢日志中很多。当表有500,000行时,查询可能需要10秒以上。问题是这个表自首次引入以来已经大量增长,现在有时有50万行,开销为133.9 MiB。例如,我们每天插入6000行新行或许180次,并删除大致相同的数字。

要停止出现在慢速日志中的查询,我们删除了ORDER BY id以停止整个表格的大规模排序。即。

mysql_query("UPDATE email_queue SET processingID = '1' WHERE processingID = 0 LIMIT 1");

...但新查询不再总是获得id最低的行(尽管通常会这样)。除了使用ORDER BY id之外,是否有更有效的方法来获取id最低的行?

作为参考,这是电子邮件队列表的结构:

CREATE TABLE IF NOT EXISTS `email_queue` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `time_queued` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Time when item was queued',
  `mem_id` int(10) NOT NULL,
  `email` varchar(150) NOT NULL,
  `processingID` int(2) NOT NULL COMMENT 'Indicate if row is being processed',
  PRIMARY KEY (`id`),
  KEY `processingID` (`processingID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

5 个答案:

答案 0 :(得分:3)

答案 1 :(得分:1)

听起来你有其他进程锁定表,以防止你的更新及时完成 - 你考虑过使用innodb吗?

答案 2 :(得分:1)

我认为'缓慢部分'来自

WHERE processingID = 0 

它很慢,因为它没有编入索引。但是,索引此列(恕我直言)似乎也是错误的。 我们的想法是将上述查询更改为:

WHERE id = 0 

理论上它会更快,因为它使用索引。

如何创建另一个包含尚未处理的id行的表?因此插入工作两次。首先插入真实表,第二个是将id插入“未处理的表”中。加工部分也需要加倍工作。首先从'尚未处理的表'中检索id然后将其删除。处理部分的第二项工作当然是处理。

当然,'表中尚未处理'中的id列需要索引其内容。只是为了确保选择和删除更快。

答案 3 :(得分:1)

这个问题很旧,但是对于任何在这里结束的人都可以参考:

您有一个处理ID(WHERE processingID = 0)的条件,并且要在该约束内要按ID订购。

当前查询正在发生的事情是,它从最低ID到最大ID扫描表,并在找到1条符合条件的记录时停止扫描。据推测,它将首先找到大量旧记录,扫描几乎整个表,直到在末尾找到未处理的记录。

我们如何改善呢?

请考虑您在processingID上有一个索引。从技术上讲,总是会附加主键(这是索引可以首先“指向”任何东西的方式)。因此,您确实processingID, id上有一个索引。这意味着订购将很快。

将您的订单更改为: ORDER BY processingID, id

由于WHERE子句已将processingID固定为单个值,因此这不会更改结果顺序。但是,它确实使数据库容易轻松地应用条件和订购,而无需扫描不匹配的任何记录。< / p>

答案 4 :(得分:0)

一个有趣的事情是,默认情况下,MySQL会返回按ID排序的行,而是以关系理论中所述的随意方式返回(我不确定此行为是否在最新版本中更改)。因此,从select中获取的最后一行应该是最后插入的行。当然,我不会用这种方式。

正如你所说,最好的解决方案是使用像Resque,RabbitMQ&amp;共

你可以使用内存表,它是易失性的,但比存储更快,有最新的ID,或者只是使用my_isam表来增加持久性。它性能简单快速,实现起来需要一点点。