我有一个有大约100.000篇博客帖子的表格,通过1:n关系链接到一个有50个提要的表格。当我使用select语句查询这两个表时(按postings表的日期时间字段排序),MySQL总是使用文件排序,这导致查询时间非常慢(>1秒)。以下是postings表的模式(简化):
+---------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| feed_id | int(11) | NO | MUL | NULL | |
| crawl_date | datetime | NO | | NULL | |
| is_active | tinyint(1) | NO | MUL | 0 | |
| link | varchar(255) | NO | MUL | NULL | |
| author | varchar(255) | NO | | NULL | |
| title | varchar(255) | NO | | NULL | |
| excerpt | text | NO | | NULL | |
| long_excerpt | text | NO | | NULL | |
| user_offtopic_count | int(11) | NO | MUL | 0 | |
+---------------------+--------------+------+-----+---------+----------------+下面是feed表:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| type | int(11) | NO | MUL | 0 | |
| title | varchar(255) | NO | | NULL | |
| website | varchar(255) | NO | | NULL | |
| url | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+下面是执行时间大于1秒的查询。请注意,post_date字段有一个索引,但MySQL没有使用它对postings表进行排序:
SELECT
`postings`.`id`,
UNIX_TIMESTAMP(postings.post_date) as post_date,
`postings`.`link`,
`postings`.`title`,
`postings`.`author`,
`postings`.`excerpt`,
`postings`.`long_excerpt`,
`feeds`.`title` AS feed_title,
`feeds`.`website` AS feed_website
FROM
(`postings`)
JOIN
`feeds`
ON
`feeds`.`id` = `postings`.`feed_id`
WHERE
`feeds`.`type` = 1 AND
`postings`.`user_offtopic_count` < 10 AND
`postings`.`is_active` = 1
ORDER BY
`postings`.`post_date` desc
LIMIT
15 此查询上的explain extended命令的结果显示,MySQL正在使用文件排序:
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+
| 1 | SIMPLE | postings | ref | feed_id,is_active,user_offtopic_count | is_active | 1 | const | 30996 | Using where; Using filesort |
| 1 | SIMPLE | feeds | eq_ref | PRIMARY,type | PRIMARY | 4 | feedian.postings.feed_id | 1 | Using where |
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+当我删除order by部件时,MySQL停止使用文件排序。如果您对如何优化此查询以便让MySQL通过使用索引对数据进行排序和选择有任何想法,请让我知道。我已经尝试了一些方法,比如像一些博客文章所建议的那样,在所有的where/order by字段上创建一个组合索引,但这也不起作用。
发布于 2009-04-03 17:55:21
在postings (is_active, post_date)上(按该顺序)创建一个复合索引。
它将用于对is_active进行过滤和按post_date排序。
MySQL应在EXPLAIN EXTENDED中显示对此索引的REF访问方法。
请注意,您在user_offtopic_count上有一个RANGE过滤条件,这就是为什么您不能在过滤和按其他字段排序时对此字段使用索引。
根据user_offtopic_count的选择性(即有多少行满足user_offtopic_count < 10),在user_offtopic_count上创建索引并对post_dates进行排序可能更有用。
为此,请在postings (is_active, user_offtopic_count)上创建一个复合索引,并确保使用此索引上的RANGE访问方法。
哪个索引更快取决于您的数据分布。创建这两个索引,对它们执行FORCE操作,看看哪一个更快:
CREATE INDEX ix_active_offtopic ON postings (is_active, user_offtopic_count);
CREATE INDEX ix_active_date ON postings (is_active, post_date);
SELECT
`postings`.`id`,
UNIX_TIMESTAMP(postings.post_date) as post_date,
`postings`.`link`,
`postings`.`title`,
`postings`.`author`,
`postings`.`excerpt`,
`postings`.`long_excerpt`,
`feeds`.`title` AS feed_title,
`feeds`.`website` AS feed_website
FROM
`postings` FORCE INDEX (ix_active_offtopic)
JOIN
`feeds`
ON
`feeds`.`id` = `postings`.`feed_id`
WHERE
`feeds`.`type` = 1 AND
`postings`.`user_offtopic_count` < 10 AND
`postings`.`is_active` = 1
ORDER BY
`postings`.`post_date` desc
LIMIT
15
/* This should show RANGE access with few rows and keep the FILESORT */
SELECT
`postings`.`id`,
UNIX_TIMESTAMP(postings.post_date) as post_date,
`postings`.`link`,
`postings`.`title`,
`postings`.`author`,
`postings`.`excerpt`,
`postings`.`long_excerpt`,
`feeds`.`title` AS feed_title,
`feeds`.`website` AS feed_website
FROM
`postings` FORCE INDEX (ix_active_date)
JOIN
`feeds`
ON
`feeds`.`id` = `postings`.`feed_id`
WHERE
`feeds`.`type` = 1 AND
`postings`.`user_offtopic_count` < 10 AND
`postings`.`is_active` = 1
ORDER BY
`postings`.`post_date` desc
LIMIT
15
/* This should show REF access with lots of rows and no FILESORT */发布于 2009-04-03 17:52:32
MySQL有两种文件排序算法:一种是在磁盘上对记录进行排序的旧文件排序算法,另一种是在内存中工作的新版本。
如果它不能在联接中的第一个表上使用索引来对查询进行排序,它将不得不执行文件排序。如果排序前转换为固定宽度格式的结果集大于排序缓冲区,或者如果它包含任何文本字段,则必须使用速度较慢的磁盘文件排序算法(由于查询具有文本字段,因此满足第二个条件)。
MySQL选择使用is_active列,表面上是因为它认为在继续其他连接和where条件之前,该列在消除行方面具有最大的选择性。我建议的第一件事是尝试使用post_date、feed_id和where条件中的列创建复合索引,例如(is_active、user_offtopic_count、post_date、feed_id)。
发布于 2009-08-25 05:41:53
此外,重要的是要记住,如果排序依据的列应用了函数,则MySQL不会使用索引。
您还应该尝试将postings.post_date的别名设置为其他名称。这将告诉MySQL按未更改的列排序,而您仍将选择unix时间戳。
https://stackoverflow.com/questions/714950
复制相似问题