【发布时间】: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