按结果优化查询顺序以使用文件排序;
Posted
技术标签:
【中文标题】按结果优化查询顺序以使用文件排序;【英文标题】:optimizing query order by results to Using filesort; 【发布时间】:2012-03-16 09:35:45 【问题描述】:查询:
SELECT
r.reply_id,
r.msg_id,
r.uid,
r.body,
r.date,
u.username as username,
u.profile_picture as profile_picture
FROM
pm_replies as r
LEFT JOIN users as u
ON u.uid = r.uid
WHERE
r.msg_id = '784351921943772258'
ORDER BY r.date DESC
我尝试了所有我能想到的索引组合,在谷歌中搜索了我如何最好地索引它,但没有任何效果。
此查询在 500 个返回的项目上采用 0,33 并计数...
解释:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE r ALL index1 NULL NULL NULL 540 Using where; Using filesort
1 SIMPLE u eq_ref uid uid 8 site.r.uid 1
显示创建 pm_replies
CREATE TABLE `pm_replies` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`reply_id` bigint(20) NOT NULL,
`msg_id` bigint(20) NOT NULL,
`uid` bigint(20) NOT NULL,
`body` text COLLATE utf8_unicode_ci NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `index1` (`msg_id`,`date`,`uid`)
) ENGINE=MyISAM AUTO_INCREMENT=541 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
显示创建用户
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uid` bigint(20) NOT NULL,
`username` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`email` text CHARACTER SET latin1 NOT NULL,
`password` text CHARACTER SET latin1 NOT NULL,
`profile_picture` text COLLATE utf8_unicode_ci NOT NULL,
`date_registered` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uid` (`uid`),
UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=2004 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
【问题讨论】:
可以添加SHOW CREATE TABLE pm_replies
,SHOW CREATE TABLE users
,EXPLAIN SELECT <your entire select here>
的输出吗?除此之外,可能的索引将是 r.msg_id, r.uid
并且您希望 u.uid
也有一个索引(最好是唯一的)。
您是否为 u.uid、r.uid、r.msg_id 和 r.date 编制了索引?编辑:我明白了...尝试在 pm_replies 中索引日期
@Uriel_SVK 没错
@fx-user - 您需要复合/覆盖索引。一个涵盖多个字段的索引。使用KEY IndexN (msg_id, date)
,您可以获得按日期排序的所有记录对于相同的msg_id。因此,当您选择一个 msg_id 时,它们都会为您按日期预先排序。请参阅下面的答案。
【参考方案1】:
对于查询,最好的索引似乎是......
pm_replies: (msg_id, date, uid)
users: (uid)
重要的是pm_replies
。您可以使用它来过滤数据(过滤列在前),然后对数据进行排序(顺序列在后)。
如果您删除过滤器,情况会有所不同。那么你只需要(date, uid)
作为你的索引。
索引中的最后一个字段只是使它对连接更友好,重要的部分实际上是users
上的索引。
在这方面还有很多可以说的,至少在一本书中的一整章,如果你愿意的话,还有几本书。但我希望这会有所帮助。
编辑
并不是我建议的pm_replies
索引是一个涵盖三个字段的索引,而不仅仅是三个索引。这可确保索引中的所有条目都按这些列进行预排序。这就像在 Excel 中按三列对数据进行排序。
拥有三个独立的索引就像在三个选项卡上拥有 Excel 数据。每个都按不同的字段排序。
只有三个字段上的一个索引才会出现这种行为... - 您可以选择具有相同 msg_id 的“一堆”记录 - 整个“一堆”彼此相邻,没有间隙等 - 整个“一堆”按该 msg_id 的日期顺序排序 - 对于任何具有相同日期的行,它们都按 user_id 排序
(同样,user_id 部分确实非常小。)
【讨论】:
所以您是说要创建 1 个包含 3 列的索引?如果是这样,这似乎仍然没有帮助。 @fxuser - 除非它是 MyISAM 的奇怪之处,否则我不确定。您的表中实际存在多少条记录?另外,您可以尝试单独创建索引吗?CREATE INDEX ix_pm_replies_2 ON pm_replies (msg_id, date, user_id)
@dems 表中有大约 540 条记录,我在不限制查询的情况下将它们全部提取出来。一个解决方案可能是添加限制,即使我不想这样做。我添加了您的索引,这与我的相同,但似乎也不起作用。
@fxuser - 当您说“全部拉取”时,您的意思是您要删除 WHERE 子句并获取所有 msg_id?
不,查询如上所示,但目前所有这些都是虚拟数据,仅由我生成。所以我正在提取属于我的用户的 540 条记录,这似乎需要很长时间......但添加限制将解决问题,但需要其他操作来加载旧记录【参考方案2】:
请试试这个:
SELECT
r.reply_id,
r.msg_id,
r.uid,
r.body,
r.date,
u.username as username,
u.profile_picture as profile_picture
FROM
pm_replies as r
LEFT JOIN users as u
ON (u.uid = r.uid AND r.msg_id = '784351921943772258')
ORDER BY r.date DESC
在我的情况下它会有所帮助。
【讨论】:
这在功能上有所不同,而且很可能不正确。 OP 基于msg_id
从pm_replies
获取一组特定的行,然后将它们左连接到users
。 (可能只有一行。) 您从pm_replies
获取所有 行,但仅将它们加入users
以获得特定的msg_id
,所有其他人仍然通过,但作为 NULL。这不是答案。
你索引字段 r.msg_id 和 r.date 了吗?
@Miro 是的,两列都已编入索引【参考方案3】:
将日期添加到您的 index1 键中,以便 msg_id 和日期都在索引中。
【讨论】:
【参考方案4】:Dems is saying 应该是正确的,但是如果您使用 InnoDB,还有一个额外的细节:也许您正在支付 secondary indexes on clustered tables 的价格 - 本质上,通过二级索引访问一行需要 额外的 通过主索引进行查找,即聚类索引。这种“双重查找”可能会降低索引对查询优化器的吸引力。
要缓解这种情况,请尝试covering 所有您的 select 语句中带有索引的字段:
pm_replies: (msg_id, date, uid, reply_id, body, date)
users: (uid, username, profile_picture)
【讨论】:
我所有的表都是 myisam,我改成 innodb 只是为了测试 dems 评论。 @fxuser 还有一件事:您是否尝试过使用索引pm_replies: (msg_id, uid)
,因为这是一个左连接,而users
是“外部”表。另外,您是否尝试删除 LEFT,只是为了看看会发生什么?
@fxuser 甚至pm_replies: (uid, msg_id)
?
尝试添加 2 列索引,但任何一种方式都没有帮助...仍然使用文件排序和加载时间是相同的...即使我删除了左连接。【参考方案5】:
看来优化器正试图通过 ID 强制索引来连接用户表。由于您正在进行左连接(这没有意义,因为我希望每个条目都有一个用户 ID,因此是一个正常的 INNER JOIN),我将保持它的左连接。
所以,我会尝试以下方法。只查询基于MESSAGE ID的回复,按日期降序排列,THEN left join,如
SELECT
r.reply_id,
r.msg_id,
r.uid,
r.body,
r.date,
u.username as username,
u.profile_picture as profile_picture
FROM
( select R2.*
from pm_replies R2
where r2.msg_id = '784351921943772258' ) r
LEFT JOIN users as u
ON u.uid = r.uid
ORDER BY
r.date DESC
此外,由于我没有现成的 mysql,并且不记得在子查询中是否允许 order by,如果允许,您可以优化内部预查询(使用别名“R2”)并放入那里的顺序,所以它使用 (msgid, date) 索引并只返回那个集合...然后加入到 ID 上的用户表,此时从 SOURCE 结果集中不需要索引,只需要索引用户表来查找匹配项。
【讨论】:
这需要更长的时间来加载,并且在 2 列(msg_id,日期)上添加 1 个索引也不起作用。 @fxuser,很抱歉它没有用,但你总是必须尝试引擎可能在想的替代方案。这是一个简单的测试,要么工作得更快,要么不工作。只是寻求性能改进的众多方法之一。以上是关于按结果优化查询顺序以使用文件排序;的主要内容,如果未能解决你的问题,请参考以下文章
Oracle查询优化改写--------------------给查询结果排序