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

Loc*_*eyu 8 mysql myisam performance index select

我正在研究跟踪使用时间的分析系统的架构,并且需要查看特定日期范围内的总使用时间。

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

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

此查询通常需要大约 7 秒的时间在一个人口密集的表上。它有大约 3500 万行,MyISAM on MySQL 在 Amazon RDS (db.m3.xlarge) 上运行。

去掉 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;
Run Code Online (Sandbox Code Playgroud)

在原始查询上运行“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;
Run Code Online (Sandbox Code Playgroud)

更新:我根据 ypercube 的响应创建了以下索引,但这会将第一次查询的查询时间增加到 17 秒左右!

ALTER TABLE writetest_table  ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;
Run Code Online (Sandbox Code Playgroud)

更新 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)
Run Code Online (Sandbox Code Playgroud)

更新 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)
Run Code Online (Sandbox Code Playgroud)

Rol*_*DBA 3

我想我开始明白了。

当我叫你跑的时候

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
Run Code Online (Sandbox Code Playgroud)

你说这是2015-07-13 15:11:56你的WHERE条款中的内容

当你进行查询时

select sum(diff_ms) from writetest_table;
Run Code Online (Sandbox Code Playgroud)

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

当你进行查询时

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

它执行了 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");
Run Code Online (Sandbox Code Playgroud)

甚至今天中午

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

应该花费更少的时间。

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