5

ORDER BY id LIMIT 1有没有比在以下查询中使用更快速的方法来更新与特定条件匹配的 MySQL 表的最旧行?

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

笔记:

  • 假设主键是id并且在 上也有一个索引field1
  • 我们正在更新一行
  • 我们不是严格更新最旧的行,而是更新与条件匹配的最旧的行。
  • 我们要更新最旧的匹配行,即最低的id,即 FIFO 队列的头部。

问题:

  • ORDER BY id必要吗?MySQL默认如何排序?

现实世界的例子

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

我们计划放弃这种方法,使用GearmanResque 之类的工具来处理我们的电子邮件队列。但与此同时,我有一个问题,我们如何有效地标记队列中最旧的项目以进行处理,也就是 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以停止整个表的大量排序。IE

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

...但是新查询不再总是获取具有最低 id 的行(尽管它经常这样做)。除了 using 之外,是否有更有效的方法来获取具有最低 id 的行ORDER BY 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;
4

5 回答 5

3

读一读:

于 2010-09-08T11:42:25.160 回答
1

我认为“缓慢的部分”来自

WHERE processingID = 0 

它很慢,因为它没有被索引。但是,索引此列(恕我直言)似乎也不正确。这个想法是将上述查询更改为:

WHERE id = 0 

由于它使用索引,理论上会更快。

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

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

于 2010-09-08T11:42:55.577 回答
1

听起来您有其他进程锁定表,阻止您的更新及时完成 - 您是否考虑过使用 innodb ?

于 2010-09-08T11:35:35.500 回答
1

这个问题很老,但供任何人在这里结束参考:

您对 processingID ( WHERE processingID = 0) 有一个条件,并且在该约束内您希望按 ID 排序。

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

我们如何改进这一点?

考虑你有一个关于processingID. 从技术上讲,始终附加主键(这就是索引首先“指向”任何内容的方式)。所以你真的有一个索引processingID, id。这意味着订购会很快。

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

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

于 2018-08-17T08:41:09.217 回答
0

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

正如您所说,最好的解决方案是使用 Resque 或 RabbitMQ & co 之类的东西。

您可以使用内存中的表,它是易失的,但比存储最新的 ID 快得多,或者只使用 my_isam 表来增加持久性。它在性能上简单而快速,并且需要一点点来实现。

于 2010-09-08T11:44:15.247 回答