如何在这个简单的查询中避免使用filesort? (没有加入)

时间:2013-11-17 17:40:12

标签: mysql sql

我制作了一个数据点表 - 它们有一个键(数据类型),值(数据值),时间戳(记录时间数据)。

表格定义:

CREATE TABLE IF NOT EXISTS datapoints (
  point_id int(11) NOT NULL AUTO_INCREMENT,
  point_user_id int(11) NOT NULL,
  point_key varchar(32) NOT NULL,
  point_value longtext NOT NULL,
  point_timestamp int(11) NOT NULL,
  PRIMARY KEY (point_id),
  KEY datapoint_search (point_key,point_timestamp,point_user_id,point_value(64))
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=0;

现在我创建一个查询,获取日期范围(在我们的示例中为1天)的特定类型的所有数据点,并按数据点值按降序排序:

EXPLAIN SELECT * FROM datapoints
WHERE point_key = 'body_temperature'
      AND point_timestamp >= UNIX_TIMESTAMP('2013-11-20')
      AND point_timestamp < UNIX_TIMESTAMP('2013-11-21')
      AND point_user_id = 1
ORDER BY point_value DESC;

不幸的是,这会在EXPLAIN:

中生成一个文件排序
id | select_type | table      | type  | possible_keys    | key              | key_len | ref  | rows   | Extra                       
 1 | SIMPLE      | datapoints | range | datapoint_search | datapoint_search | 106     | NULL | 175642 | Using where; Using filesort

是否可以避免此文件?我已经创建了一个索引(datapoint_search),并且它已被使用,但仍然会调用一个filesort。

PS。 point_value列必须是text或longtext,或者至少处理非常大的数据(最多8KB),同时仍然可以排序。

4 个答案:

答案 0 :(得分:3)

以下索引满足您的where子句:

datapoints(point_key, point_user_id, point_timestamp);

这可能会显着提高查询的性能,但不会删除文件排序。

理论上可以是:

datapoints(point_key, point_user_id, point_value, point_timestamp);

但是,我不认为MySQL足够聪明,可以匹配where子句和order by的一部分,并在排序后完成剩余的过滤。值得一试。

以下内容不起作用:

datapoints(point_key, point_user_id, point_timestamp, point_value);

将按时间戳顺序检索数据以满足where子句。 point_value的排序是次要的时间戳。

编辑:

如果where找到的行数是“常量”,那么性能应该相似。如果您与point_key point_user_id没有太多匹配,那么以下技巧可能有所帮助:

select dp.*
from (SELECT *
      FROM datapoints
      WHERE point_key = 'body_temperature' AND point_user_id = 1
      ORDER BY point_value DESC
     ) dp
where point_timestamp >= UNIX_TIMESTAMP('2013-11-20') AND
      point_timestamp < UNIX_TIMESTAMP('2013-11-21');

与索引datapoints(point_key, point_user_id, point_value)一起。

不幸的是,MySQL并不保证内部子查询中的排序实际上为外部查询保留了行(我认为它在实践中确实存在,至少通常是这样)。这将使用内部查询的索引,然后扫描第二个where子句的临时表。

此外,如果您不需要所有列,那么我建议您将所需的列放入索引中。这将在匹配时保存完整表的随机扫描。

答案 1 :(得分:1)

在对point_value进行排序时,

Filesort不会消失。 point_value的索引仅为64字节。排序是通过整个数据完成的。 我建议将point_value_64_prefix存储用于搜索和排序point_value

这也有问题。排序只完成64个字节,排序结果不完全正确。但在大多数情况下,64字节就足够了(我猜)

CREATE TABLE IF NOT EXISTS datapoints (
  point_id int(11) NOT NULL AUTO_INCREMENT,
  point_user_id int(11) NOT NULL,
  point_key varchar(32) NOT NULL,
  point_value longtext NOT NULL,
  point_value_64_prefix VARCHAR(64) NOT NULL, // <= this column added
  point_timestamp int(11) NOT NULL,
  PRIMARY KEY (point_id),
  KEY datapoint_search (point_key,point_timestamp,point_user_id,point_value_64_preifx) // <= 
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=0;

EXPLAIN SELECT * FROM datapoints
WHERE point_key = 'body_temperature'
      AND point_timestamp >= UNIX_TIMESTAMP('2013-11-20')
      AND point_timestamp < UNIX_TIMESTAMP('2013-11-21')
      AND point_user_id = 1
ORDER BY point_value_64_prefix DESC // <= sort by point_value_64_prefix rather than original value.

并且,如果您的排序数据很大,在这种情况下可能会发生Filesort,您需要增加MySQL临时表大小。见http://dev.mysql.com/doc/refman/5.1/en/internal-temporary-tables.html

手册说:

  

内存临时表的最大大小是tmp_table_size和max_heap_table_size值的最小值

答案 2 :(得分:0)

如果您在特定的日期范围内搜索特定point_value和point_user_id的行,就像在查询中一样,您的索引应该打开(point_key,point_user_id,point_timestamp)。将point_timestamp放在索引中的第二位不会对你有所帮助,除非你有大量具有相同point_timestamp值的行(这可能不太可能)。

答案 3 :(得分:0)

MySQL可以使用filesort,但它仍然可以在内存中,然后性能仍然很好。

Bill Karwin在这里给出了一个很好的解释:Any way to avoid a filesort when order by is different to where clause?