按结果优化查询顺序以使用文件排序;

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_idpm_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,很抱歉它没有用,但你总是必须尝试引擎可能在想的替代方案。这是一个简单的测试,要么工作得更快,要么不工作。只是寻求性能改进的众多方法之一。

以上是关于按结果优化查询顺序以使用文件排序;的主要内容,如果未能解决你的问题,请参考以下文章

SQL Query 总是按子句顺序使用文件排序

如何先按搜索词排序查询结果,然后按字母顺序?

按字母顺序对查询结果进行排序,但在排序中排除“the”?

Oracle查询优化改写--------------------给查询结果排序

按查询的where子句中的字段顺序对sql查询的结果进行排序

Hadoop 二次排序