【问题标题】:Simple query takes 15-30 seconds简单查询需要 15-30 秒
【发布时间】:2009-11-22 14:36:25
【问题描述】:

以下查询非常简单。它从消息表中选择最后 20 条记录用于分页场景。第一次运行此查询时,需要 15 到 30 秒。随后的运行需要不到一秒钟的时间(我预计会涉及一些缓存)。我正在尝试确定为什么第一次需要这么长时间。

这是查询:

SELECT DISTINCT ID,List,`From`,Subject, UNIX_TIMESTAMP(MsgDate) AS FmtDate
FROM messages
WHERE List='general'
ORDER BY MsgDate
LIMIT 17290,20;

MySQL 版本:4.0.26-log

这是桌子:

messages  CREATE TABLE `messages` (
  `ID` int(10) unsigned NOT NULL auto_increment,
  `List` varchar(10) NOT NULL default '',
  `MessageId` varchar(128) NOT NULL default '',
  `From` varchar(128) NOT NULL default '',
  `Subject` varchar(128) NOT NULL default '',
  `MsgDate` datetime NOT NULL default '0000-00-00 00:00:00',
  `TextBody` longtext NOT NULL,
  `HtmlBody` longtext NOT NULL,
  `Headers` text NOT NULL,
  `UserID` int(10) unsigned default NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `List` (`List`,`MsgDate`,`MessageId`),
  KEY `From` (`From`),
  KEY `UserID` (`UserID`,`List`,`MsgDate`),
  KEY `MsgDate` (`MsgDate`),
  KEY `ListOnly` (`List`)
) TYPE=MyISAM ROW_FORMAT=DYNAMIC

解释如下:

table   type    possible_keys  key       key_len  ref       rows  Extra
------  ------  -------------  --------  -------  ------  ------  --------------------------------------------
m       ref     List,ListOnly  ListOnly  10       const    18002  Using where; Using temporary; Using filesort

当我在所有相关列上都有索引时,为什么要使用文件排序?我添加了 ListOnly 索引只是为了看看它是否有帮助。我原本以为 List 索引可以同时处理列表选择和 MsgDate 上的排序,但事实并非如此。现在我添加了 ListOnly 索引,这就是它使用的索引,但它仍然对 MsgDate 进行文件排序,我怀疑这需要很长时间。

我尝试如下使用 FORCE INDEX:

SELECT DISTINCT ID,List,`From`,Subject, UNIX_TIMESTAMP(MsgDate) AS FmtDate
FROM messages
FORCE INDEX (List)
WHERE List='general'
ORDER BY MsgDate
LIMIT 17290,20;

这似乎确实迫使 MySQL 使用索引,但它根本不会加快查询速度。

下面是这个查询的解释:

table   type    possible_keys  key     key_len  ref       rows  Extra                       
------  ------  -------------  ------  -------  ------  ------  ----------------------------
m       ref     List           List    10       const    18002  Using where; Using temporary

更新:

我从查询中删除了 DISTINCT。这对性能没有任何帮助。

我删除了 UNIX_TIMESTAMP 调用。它也不影响性能。

我在我的 PHP 代码中做了一个特殊情况,如果我检测到用户正在查看结果的最后一页,我会添加一个 WHERE 子句,它只返回最后 7 天的结果:

SELECT m.ID,List,From,Subject,MsgDate
FROM messages
WHERE MsgDate>='2009-11-15'
ORDER BY MsgDate DESC
LIMIT 20

这要快得多。但是,当我导航到另一页结果时,它必须使用旧 SQL 并且需要很长时间才能执行。我想不出一种实用、现实的方法来为所有页面执行此操作。此外,执行这种特殊情况会使我的 PHP 代码更加复杂。

奇怪的是,只有第一次运行原始查询需要很长时间。随后运行相同查询或显示不同结果页面的查询(即,仅 LIMIT 子句更改)非常快。如果大约 5 分钟未运行查询,它会再次变慢。

解决方案:

我想出的最佳解决方案是基于 Jason Orendorff 和 Juliet 的想法。

首先,我确定当前页面是否更接近总页数的开头或结尾。如果接近尾声,我使用 ORDER BY MsgDate DESC,应用适当的限制,然后反转返回记录的顺序。

这使得检索接近结果集开头或结尾的页面更快(现在第一次需要 4-5 秒而不是 15-30 秒)。如果用户想要导航到靠近中间的页面(目前在第 430 页左右),那么速度可能会回落。但这种情况很少见。

因此,虽然似乎没有完美的解决方案,但这比大多数情况下要好得多。

谢谢你,杰森和朱丽叶。

【问题讨论】:

  • 好点,害羞。我会在没有 DISTINCT 的情况下尝试一下。
  • 删除 DISTINCT 根本没有提高性能。
  • “LIMIT 17290,20”不是对应您查询的第 865 页吗?用户真的在数据集中导航那么远吗?
  • 之后会更快,因为 MySQL 正在缓存查询的结果。请注意,explain 的两个结果都显示 MySQL 生成了一个 18002 行的结果集。
  • Juliet,默认视图是最新的一组消息,因此是第 865 页。

标签: mysql performance limit


【解决方案1】:

试试ORDER BY MsgDate DESC LIMIT 20,而不是ORDER BY MsgDate LIMIT 17290,20

当然结果会以相反的顺序出现,但这应该很容易处理。

编辑:您的MessageId 值是否总是随时间增加?它们是独一无二的吗?

如果是这样,我会做一个索引:

UNIQUE KEY `ListMsgId` ( `List`, `MessageId` )

并尽可能根据消息 ID 而不是日期进行查询。

-- Most recent messages (in reverse order)
SELECT * FROM messages
WHERE List = 'general'
ORDER BY MessageId DESC
LIMIT 20

-- Previous page (in reverse order)
SELECT * FROM messages
WHERE List = 'general' AND MessageId < '15885830'
ORDER BY MessageId DESC
LIMIT 20

-- Next page
SELECT * FROM messages
WHERE List = 'general' AND MessageId > '15885829'
ORDER BY MessageId
LIMIT 20

我认为您还需要为 varchar 列付费,而 int 类型会更快。例如,List 可以改为指向单独表中的条目的ListId。您可能想在测试数据库中尝试一下,看看是否真的如此;我不是 MySQL 专家。

【讨论】:

  • 我使用 LIMIT 是因为用户可以翻页到前 20 条消息(或之前的 20 条消息等)。
  • 我试过这个,但它根本没有帮助。这可能是因为无论是否使用偏移量,LIMIT 都有相同的问题。
  • 哇。这有点奇怪。此查询的最简单可能形式是 SELECT * FROM messages WHERE List='general' ORDER BY MsgDate,因为您在 (List, MsgDate, MessageId) 上有一个索引,所以我希望它非常快。
  • 我已经尝试过这个: SELECT * FROM messages WHERE List = 'general' ORDER BY MsgDate DESC LIMIT 20 它并没有更快,因为在处理 LIMIT 之前检索了整个行集。在 MessageID 上订购不会更好。顺便说一句,MessageID 是一个电子邮件 MessageID 标头,因此它不会是连续的(尽管 ID 是)。
  • 我想知道大的 HtmlBody 和 TextBody 列是否是问题的一部分,即使这里没有检索到它们。
【解决方案2】:

您可以删除ListOnly 键。复合索引List 已经包含了其中的所有信息。

您对List-indexed 查询的解释看起来好多了,缺少文件排序。您可以通过按照 Jason 的建议交换 ORDER 来获得更好的实际性能,并且可能会丢失 UNIX_TIMESTAMP 调用(您可以在应用程序层执行此操作,或者仅使用模式中存储为 INTEGER 的 Unix 时间戳)。

【讨论】:

  • 丢失 UNIX_TIMESTAMP 真的会对性能有那么大的影响吗?
  • 真的不知道,只能测试看看。我希望不会,但经常使用计算列会导致 MySQL 使用临时表,否则它可能不需要。就我个人而言,我只对日期使用 unix 时间戳,因为原生日期类型往往会导致跨 DBMS 的访问层不兼容,并且与普通时间戳相比,提供的实用性相对较小。
  • 我尝试删除 UNIX_TIMESTAMP。它对性能没有任何帮助。
【解决方案3】:

您使用的是什么版本的 SQL?一些旧版本使用 LIMIT 子句作为后处理过滤器(意味着获取服务器请求的所有记录,但只显示您请求返回的 20 条)。

您可以从您的解释中看到,18002 行正在返回,即使您只显示其中的 20 行。有没有办法调整你的选择标准来识别你想要返回的 20 行,而不是返回 18000 行并且只显示其中的 20 行???

【讨论】:

  • 4.0.26:绝对是比我今天想使用的旧版本的 MySQL。
  • 这是有道理的,您的第一个查询返回了 18002 行,尽管它只显示了其中的 20 行。 (您的解释表明了这一点)。看起来其他人也提供了相同的解决方案,尝试通过 WHERE 子句取回几行,而不是依赖 LIMIT 选项。如果您正在寻找最新的记录,您可能想要获取最大 msgdate,然后在 where 子句中使用该日期以及之前的日期。即使这是两个查询,一个用于获取最大日期,一个用于获取数据,它应该运行得更快。祝你好运
  • 我在我的 PHP 代码中做了一个特殊情况,这样如果我检测到用户正在查看结果的最后一页,我添加一个 WHERE 子句,它只返回最后 7 天的结果:SELECT m .ID,List,From,Subject,MsgDate FROM messages m WHERE MsgDate>='2009-11-15' ORDER BY MsgDate DESC LIMIT 20 这要快得多。但是,一旦我导航到另一页结果,它就会使用旧的 SQL,并且需要永远返回。我想不出一种切实可行的方法来对所有页面执行此操作。
  • 另外...我的 PHP 代码在检查特殊情况时变得丑陋。
猜你喜欢
  • 2019-02-28
  • 2019-08-01
  • 1970-01-01
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-18
  • 2012-12-16
相关资源
最近更新 更多