使用 LEFT JOIN + ORDER BY 时如何避免 FileSort?
Posted
技术标签:
【中文标题】使用 LEFT JOIN + ORDER BY 时如何避免 FileSort?【英文标题】:How to avoid FileSort when using LEFT JOIN + ORDER BY? 【发布时间】:2016-03-10 03:25:13 【问题描述】:mysql-5.6.24-win32.1432006610
我有两个简单的表,TUser(id, name)
和 TMessage(id, uid, message)
。 TUser
保存用户,TMessage
保存用户消息。
SQL如下,另见:http://sqlfiddle.com/#!9/7f099
CREATE TABLE TUser(
id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(128) NOT NULL
);
CREATE TABLE TMessage(
id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
uid INT UNSIGNED NOT NULL,
message VARCHAR(256) NOT NULL
);
CREATE INDEX TMessageIndexUid ON TMessage(uid);
插入一些数据:
INSERT INTO TUser (name) VALUES
('jack')
,('rose')
,('peter');
INSERT INTO TMessage(uid, message) VALUES
(1, 'Hello jack')
, (1, 'Jack, how are you')
, (1, 'Good morning jack')
, (2, 'I love you, rose')
, (3, 'Peter, please call back')
, (3, 'What are you doing, Peter');
当我运行以下 LEFT JOIN + ORDER BY 查询时,FileSort 显示在 EXPLAIN 结果中:
EXPLAIN
SELECT *
FROM TUser
LEFT JOIN TMessage
ON TUser.id=TMessage.uid
WHERE TUser.id=3
ORDER BY TMessage.id DESC;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE TUser const PRIMARY; PRIMARY 4 const 1 Using temporary; Using filesort
1 SIMPLE TMessage ref TMessageIndexUid TMessageIndexUid 4 const 2 \N
有什么问题吗?
【问题讨论】:
【参考方案1】:文件排序由 ORDER BY 引入:
mysql> EXPLAIN
-> SELECT *
-> FROM TUser
-> LEFT JOIN TMessage
-> ON TUser.id=TMessage.uid
-> WHERE TUser.id=3
-> ORDER BY TMessage.id DESC;
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+---------------------------------+
| 1 | SIMPLE | TUser | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using temporary; Using filesort |
| 1 | SIMPLE | TMessage | NULL | ref | TMessageIndexUid | TMessageIndexUid | 4 | const | 4 | 100.00 | NULL |
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+---------------------------------+
2 rows in set, 1 warning (0.00 sec)
mysql> EXPLAIN
-> SELECT *
-> FROM TUser
-> LEFT JOIN TMessage
-> ON TUser.id=TMessage.uid
-> WHERE TUser.id=3;
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | TUser | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 1 | SIMPLE | TMessage | NULL | ref | TMessageIndexUid | TMessageIndexUid | 4 | const | 4 | 100.00 | NULL |
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
由于 order by 对 LEFT JOIN 结果进行操作,我不知道如何避免这种文件排序。
如果将 LEFT JOIN 更改为 INNER JOIN 是可以接受的,这可能是一种绕过方式,同时会丢失一些与 TMessages 不匹配的用户信息。
mysql> EXPLAIN
-> SELECT *
-> FROM TUser
-> INNER JOIN TMessage
-> ON TUser.id=TMessage.uid
-> WHERE TUser.id=3
-> ORDER BY TMessage.id DESC;
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | TUser | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 1 | SIMPLE | TMessage | NULL | ref | TMessageIndexUid | TMessageIndexUid | 4 | const | 2 | 100.00 | Using where |
+----+-------------+----------+------------+-------+------------------+------------------+---------+-------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
【讨论】:
文件排序可以避免吗? ORDER BY 有必要吗? 帖子已更新。更改为 INNER JOIN 是否可以接受? @扎克 谢谢。是不是因为TMessage.id
列可能使用LEFT JOIN得到NULL值,所以TMessage.id
上的索引不会被使用?
我不太确定原因。正如我猜测的那样,使用 LEFT JOIN 后,行可能与原始左表不同,因此 id 上的原始索引将无法使用。在这种情况下,行号与左表相同是巧合。这取决于连接条件,例如,如果 'ON TUser. id != TMessage.uid' 用作连接条件,行数会有所不同。【参考方案2】:
我也不知道“文件排序”。然后我发现了类似的东西,
"Anytime a sort can’t be performed from an index, it’s a filesort."
以下是参考链接:
https://www.percona.com/blog/2009/03/05/what-does-using-filesort-mean-in-mysql/ http://s.petrunia.net/blog/?p=24 Avoid filesort with INNER JOIN + ORDER BY【讨论】:
以上是关于使用 LEFT JOIN + ORDER BY 时如何避免 FileSort?的主要内容,如果未能解决你的问题,请参考以下文章
使用 LEFT JOIN 和 ORDER BY...LIMIT 查询慢,使用 Filesort
在 CTE 中使用 Order By 和 Left or right join
将 ORDER BY 添加到 LEFT OUTER JOIN
在 sql 查询中使用 LIMIT 和 ORDER BY 和 LEFT JOIN