MySQL:为啥在使用索引时仍然“使用文件排序”?

Posted

技术标签:

【中文标题】MySQL:为啥在使用索引时仍然“使用文件排序”?【英文标题】:MySQL: why still 'using filesort' when indexes are used?MySQL:为什么在使用索引时仍然“使用文件排序”? 【发布时间】:2016-03-10 13:17:53 【问题描述】:

mysql-5.6.24-win32.1432006610

我有两个用于用户消息的表格。

TMessageBody (id, body) 存储消息正文。

TMessage (id, uid, folderId, msgBodyId, subject) 将用户的消息存储在收件箱、发件箱等文件夹中。


创建表 SQL

create table TMessageBody (
    id          int unsigned    not null    primary key auto_increment,
    body        text            not null
);


create table TMessage (
    id          int unsigned    not null    primary key auto_increment,
    uid         int unsigned    not null,
    folderId    int unsigned    not null,
    msgBodyId   int unsigned    not null,
    subject     varchar(256)    not null
);

一些测试数据

insert into TMessageBody 
    (body) values 
    ('Here is body 1')
    ,('Here is body 2')
    ,('Here is body 3')
    ,('Here is body 4')
    ,('Here is body 5')
    ,('Here is body 6')
    ,('Here is body 7')
    ,('Here is body 8')
    ,('Here is body 9')
    ;

insert into TMessage
    (uid, folderId, msgBodyId, subject) values
    (1, 999, 1, 'Hello jack')
    , (1, 999, 2, 'Jack, how are you')
    , (1, 888, 3, 'Good morning jack')
    , (2, 888, 4, 'I love you, rose')
    , (2, 999, 5, 'I love you, rose')
    , (3, 888, 6, 'Peter, please call back')
    , (3, 999, 7, 'What are you doing, Peter')
    , (3, 999, 8, 'Happy birthday, perter')
    , (4, 999, 9, 'Let me know if you are ready')
    ;

索引

create index Idx_MsgBodyId      on TMessage(msgBodyId);
create index Idx_Uid_FolderId   on TMessage(uid, folderId);

1.FileSort 显示folderId 不在WHERE 子句中时

以下查询通过给定的用户 ID 获取所有消息,包括消息正文:

SET @uid=3;
SET @folderId=999;

EXPLAIN
SELECT *
FROM        TMessage
INNER JOIN  TMessageBody 
ON          TMessage.msgBodyId=TMessageBody.id
WHERE       TMessage.uid=@uid
                #AND TMessage.folderId=@folderId
ORDER BY    TMessage.id DESC
;

EXPLAIN 结果是:

mysql> EXPLAIN
-> SELECT *
->  FROM            TMessage
->  INNER JOIN      TMessageBody
->  ON              TMessage.msgBodyId=TMessageBody.id
->  WHERE           TMessage.uid=@uid
->                      #AND TMessage.folderId=@folderId
->  ORDER BY        TMessage.id DESC
->  ;
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-----------------------------+
| id | select_type | table        | type   | possible_keys                  | key              | key_len | ref                     | rows | Extra                       |
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-----------------------------+
|  1 | SIMPLE      | TMessage     | ref    | Idx_MsgBodyId,Idx_Uid_FolderId | Idx_Uid_FolderId | 4       | const                   |    3 | Using where; Using filesort |
|  1 | SIMPLE      | TMessageBody | eq_ref | PRIMARY                        | PRIMARY          | 4       | test.TMessage.msgBodyId |    1 | NULL                        |
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-----------------------------+
2 rows in set (0.00 sec)

2.当folderId在WHERE子句中时,FileSort消失

除了 WHERE 子句之外,查询与上面的查询相同:

SET @uid=3;
SET @folderId=999;

EXPLAIN
SELECT *
FROM        TMessage
INNER JOIN  TMessageBody 
ON          TMessage.msgBodyId=TMessageBody.id
WHERE       TMessage.uid=@uid
              AND TMessage.folderId=@folderId
ORDER BY    TMessage.id DESC
;

EXPLAIN 结果是:

mysql> EXPLAIN
-> SELECT *
->  FROM            TMessage
->  INNER JOIN      TMessageBody
->  ON              TMessage.msgBodyId=TMessageBody.id
->  WHERE           TMessage.uid=@uid
->                            AND TMessage.folderId=@folderId
->  ORDER BY        TMessage.id DESC
->  ;
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-------------+
| id | select_type | table        | type   | possible_keys                  | key              | key_len | ref                     | rows | Extra       |
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-------------+
|  1 | SIMPLE      | TMessage     | ref    | Idx_MsgBodyId,Idx_Uid_FolderId | Idx_Uid_FolderId | 8       | const,const             |    2 | Using where |
|  1 | SIMPLE      | TMessageBody | eq_ref | PRIMARY                        | PRIMARY          | 4       | test.TMessage.msgBodyId |    1 | NULL        |
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-------------+
2 rows in set (0.00 sec)

问题

这两个查询的区别在于folderId 列是否在WHERE 子句中。根据 EXPLAIN 结果,两个查询都使用Idx_Uid_FolderId 索引。我想知道为什么一个显示 FileSort 而另一个没有。


更新

尝试在第一个查询中使用ORDER BY TMessage.folderId, TMessage.id DESC。但是 Using filesort 仍然存在于 EXPLAIN 结果中。

mysql> EXPLAIN
-> SELECT *
->  FROM        TMessage
->  INNER JOIN  TMessageBody
->  ON          TMessage.msgBodyId=TMessageBody.id
->  WHERE       TMessage.uid=@uid
->                   #AND TMessage.folderId=@folderId
->  ORDER BY    TMessage.folderId, TMessage.id DESC
->  ;
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-----------------------------+
| id | select_type | table        | type   | possible_keys                  | key              | key_len | ref                     | rows | Extra                       |
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-----------------------------+
|  1 | SIMPLE      | TMessage     | ref    | Idx_MsgBodyId,Idx_Uid_FolderId | Idx_Uid_FolderId | 4       | const                   |    3 | Using where; Using filesort |
|  1 | SIMPLE      | TMessageBody | eq_ref | PRIMARY                        | PRIMARY          | 4       | test.TMessage.msgBodyId |    1 | NULL                        |
+----+-------------+--------------+--------+--------------------------------+------------------+---------+-------------------------+------+-----------------------------+
2 rows in set (0.06 sec)

【问题讨论】:

【参考方案1】:

将索引视为连接值。

在这种情况下,您的索引是

uid | folderId | id

id 位于最后,因为它是二级索引中对主键的引用。

在第一个场景中,您按 uid 过滤,然后按 id 排序。问题是 MySQL 不能假设 id 是按到期排序的,因为索引实际上是在被 id 过滤之前按 folderId 排序的。

例如:

uid | folderId | id
  1 |        1 |  1
  1 |        2 |  2
  2 |        1 |  3

因此该索引不能用于排序,因为索引的排序不同于order by子句。

现在,我的问题是:你为什么要避免文件排序?除非您遇到性能问题,否则使用文件排序非常好。尽管有名字,但排序是在内存中完成的,除非另有说明(使用临时)。

【讨论】:

我在第一个查询中尝试了ORDER BY TMessage.folderId, TMessage.id DESCUsing filesort 仍然存在。正如你所说,在大多数情况下是可以接受的,但我仍然想知道为什么在这种情况下。 @Zach 哦,我会从我的答案中删除那一点

以上是关于MySQL:为啥在使用索引时仍然“使用文件排序”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Postgres 在使用覆盖索引时仍然进行位图堆扫描?

在 MySQL 中进行排序时查询花费了太多时间

Mysql查询使用索引使用文件排序使用临时

MySQL 索引和使用文件排序

MySQL按查询分组,多个总和不使用索引,滞后于使用文件排序

使用 Using temporary 解释 mysql 性能中的计划;使用文件排序;使用索引条件