优化MySQL SELECT语句中TIMESTAMP字段的WHERE条件


8

我正在为一个跟踪使用时间的分析系统设计一个模式,并且需要查看特定日期范围内的总使用时间。

举一个简单的例子,这种查询类型将经常运行:

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

在人口众多的表上,此查询通常需要7秒钟左右。它有约3500万行,运行在Amazon RDS(db.m3.xlarge)上的MySQL上的MyISAM。

摆脱WHERE子句可以使查询仅花费4秒,而添加第二个子句(time_off> XXX)则需要增加1.5秒,从而使查询时间达到8.5秒。

因为我知道通常会完成这些类型的查询,所以我想优化一些东西,使其更快,最好在5秒以下。

我从在time_on上添加索引开始,尽管它大大加快了WHERE“ =”查询,但对“>”查询没有影响。有没有一种方法可以创建可以加快WHERE“>”或“ <”查询的索引?

或者,如果还有其他建议可以查询此类查询的性能,请告诉我。

注意:我使用“ diff_ms”字段作为非规范化步骤(它等于time_off-time_on),这将聚合的性能提高了大约30%-40%。

我正在使用以下命令创建索引:

ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;

在原始查询上运行“ explain”(使用“ time_on>”)时,time_on是“ possible_key”,而select_type是“ SIMPLE”。“额外”列显示“在何处使用”,“类型”为“全部”。添加索引后,该表显示“ time_on”是“ MUL”键类型,由于同一时间可以出现两次,因此这似乎是正确的。

这是表模式:

CREATE TABLE `writetest_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sessionID` int(11) DEFAULT NULL,
  `time_on` timestamp NULL DEFAULT NULL,
  `time_off` timestamp NULL DEFAULT NULL,
  `diff_ms` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;

更新:我基于ypercube的响应创建了以下索引,但这将第一次查询的查询时间增加到大约17秒!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;

更新2:解释输出

mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table               | type  | possible_keys        | key                  | key_len | ref  | rows     | Extra                    |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10      | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

更新3:请求的查询结果

mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on             |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)

您实际上在这2列(time_ondiff_ms)中是否有null ?如果添加查询会WHERE ... AND diff_ms IS NOT NULL怎样?
ypercubeᵀᴹ

能否请你告诉我们的输出SELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercubeᵀᴹ

当查询具有时,“更新2”中的说明也会显示“ 表:writetest_table_oldfrom writetest_table。是错字还是您在不同的表中运行查询?
ypercubeᵀᴹ

Answers:


3

我想我已经开始理解。

当我要求你跑步时

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

你说的就是2015-07-13 15:11:56你的WHERE条款

当您查询时

select sum(diff_ms) from writetest_table;

它执行了3580万行的全表扫描。

当您查询时

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

它执行了3580万行的完整索引扫描。

没有WHERE子句的查询更快是完全有意义的。为什么呢

表扫描将在一次线性扫描中读取3580万行。

使用WHERE进行的查询中的EXPLAIN也显示3580万行。索引扫描的行为会有所不同。尽管BTREE保持键的顺序,但进行范围扫描却很恐怖。在您的特定情况下,您正在执行最坏的范围扫描,该扫描具有与表中的行相同数量的BTREE条目。MySQL必须遍历BTREE页面(至少跨叶节点)以读取值。此外,time_on必须按照索引指示的顺序对列进行比较。因此,也必须遍历非叶BTREE节点。

请查看我在BTREE上的帖子

如果查询是今天的午夜

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

甚至今天中午

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

它应该花费更少的时间。

故事的寓意:不要使用WHERE子句来进行与目标表中的行数相等的有序范围扫描。


我唯一的问题是如何从这里出发。我用日期进行查询,结果仅过滤了100万行,总和仅用了1秒。但是有时我可能不得不对大多数数据进行汇总。有什么建议如何处理吗?我希望MySQL足够聪明,知道何时使用索引,何时不使用索引,但是我想在这种情况下它没有足够的信息。
Locksleyu

我真的希望有某种索引可以使WHERE子句快速指定日期范围,这种索引在技术上似乎可以实现,但我想它不受支持。
Locksleyu

在这么短的范围内,您有太多的数据。没有WHERE子句可以得到补偿。为什么呢 问题不是索引。这是MySQL查询优化程序对索引的看法。当您开始积累更多的数据(比如说大约需要两周的时间)时,索引统计数据应该会趋于稳定,并且性能应该会得到改善。只是不进行完整的索引扫描。
RolandoMySQLDBA 2015年

4

对于特定查询:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

索引(time_on, diff_ms)将是最好的选择。因此,如果查询经常运行,或者查询的效率对您的应用程序至关重要,请添加以下索引:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(与问题无关)
确实,将表的引擎更改为InnoDB。是2015年,而MyISAM的葬礼是几年前的事。
(/ rant)


我创建了您建议的确切索引,然后运行了您在响应中首先提到的确切查询,但是现在的时间更糟了,始终花费约17秒的时间(我尝试了几次)。
Locksleyu 2015年

我不知道是什么原因造成的。万一重要,表中只有3671个不同的time_on值(这是由于我的测试脚本填充数据的方式)。
Locksleyu 2015年

您应该执行三(3)件事:1.运行ALTER TABLE writetest_table DROP INDEX time_on;,2)运行ANALYZE TABLE writetest_table;,和3)重新运行查询。时间会回到7秒吗?
RolandoMySQLDBA 2015年

1
您还应该跑步EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");。是否正在使用新索引?如果不使用它,我会说这是您的关键人群,尤其是如果您最早的time_on是几天前。随着行数的增加和不同的日子的增加,密钥分布应该趋于平稳,EXPLAIN应该更好。
RolandoMySQLDBA 2015年

RolandoMySQLDBA-我尝试了您的三个步骤,是的,时间回到了7秒。我做了解释,并说正在使用索引。我仍然不知道为什么添加这样的索引会使性能提高2倍以上。
Locksleyu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.