我制作了一个数据点表 - 它们有一个键(数据类型),值(数据值),时间戳(记录时间数据)。
表格定义:
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),同时仍然可以排序。
答案 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)
Filesort
不会消失。
point_value
的索引仅为64字节。排序是通过整个数据完成的。
我建议将point_value_64_prefix
存储用于搜索和排序point_value
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?