MySQL 性能优化:按日期时间字段排序

Posted

技术标签:

【中文标题】MySQL 性能优化:按日期时间字段排序【英文标题】:MySQL performance optimization: order by datetime field 【发布时间】:2010-10-17 10:17:33 【问题描述】:

我有一个包含大约 100.000 个博客帖子的表格,通过 1:n 关系链接到一个包含 50 个提要的表格。当我使用 select 语句查询两个表时,按张贴表的日期时间字段排序,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 没有使用它来对发帖表进行排序:

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 字段上创建组合索引,但这也不起作用。

【问题讨论】:

我真的很喜欢你提出这个问题的清晰而详细的方式。 【参考方案1】:

postings (is_active, post_date)(按此顺序)上创建一个复合索引。

它将用于过滤is_active 和按post_date 排序。

MySQL 应该在EXPLAIN EXTENDED 中的该索引上显示REF 访问方法。

请注意,您在user_offtopic_count 上有一个RANGE 过滤条件,这就是为什么您不能在过滤和按其他字段排序时对该字段使用索引的原因。

根据您的user_offtopic_count 的选择性(即满足user_offtopic_count &lt; 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 */

【讨论】:

这对我有用,非常感谢!我不得不使用强制索引来获得使用的最佳索引。我们现在为不同的查询使用多个组合索引。 像一个魅力一样工作,强制索引确实在它所在的位置,当添加子查询时,优化器似乎把事情扔掉了。【参考方案2】:

MySQL 有两种文件排序算法:一种用于对磁盘上的记录进行排序的旧文件排序,另一种在内存中工作的新版本。

如果它不能使用连接中第一个表上的索引对查询进行排序,则必须进行文件排序。如果排序前转换为固定宽度格式的结果集大于排序缓冲区 OR 如果它包含任何文本字段,则必须使用较慢的磁盘文件排序算法(第二个条件满足您的查询有一个文本字段)。

MySQL 选择使用 is_active 列,表面上是因为它认为该列在继续使用其他连接和 where 条件之前在消除行方面最具选择性。我建议的第一件事是尝试使用 post_date、feed_id 和 where 条件中的列创建复合索引,例如(is_active、user_offtopic_count、post_date、feed_id)。

【讨论】:

【参考方案3】:

此外,重要的是要记住,如果您排序的列具有应用于它的函数,MySQL 将不会使用索引。

您还应该尝试将 posts.post_date 别名为其他名称。这将告诉 MySQL 按未更改的列排序,您仍将选择 unix 时间戳

【讨论】:

以上是关于MySQL 性能优化:按日期时间字段排序的主要内容,如果未能解决你的问题,请参考以下文章

获取按日期排序的最后记录的最佳性能方法

问SQL,按日期和时间两个字段排序?

PostgreSQL 在按日期索引的时间戳字段上按日期搜索性能不佳

按附加字段“日期”排序的条目

Postgres:按日期时间优化查询

引导表:按日期字段排序