优化未按预期使用索引的日期时间字段

时间:2011-10-19 21:09:37

标签: mysql performance datetime indexing database-performance

我在运行MySQL 5.0.77的应用程序中有一个快速增长的大型日志表。我正在尝试找到根据消息类型优化在过去X天内计算实例的查询的最佳方法:

CREATE TABLE `counters` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `kind` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_counters_on_kind` (`kind`),
  KEY `index_counters_on_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

对于此测试集,表中有668521行。我正在尝试优化的查询是:

SELECT kind, COUNT(id) FROM counters WHERE created_at >= ? GROUP BY kind;

现在,该查询需要3-5秒,并且估算如下:

+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
| id | select_type | table    | type  | possible_keys                    | key                    | key_len | ref  | rows    | Extra       |
+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | counters | index | index_counters_on_created_at_idx | index_counters_on_kind | 258     | NULL | 1185531 | Using where | 
+----+-------------+----------+-------+----------------------------------+------------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

删除created_at索引后,它看起来像这样:

+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
| id | select_type | table    | type  | possible_keys | key                    | key_len | ref  | rows    | Extra       |
+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | counters | index | NULL          | index_counters_on_kind | 258     | NULL | 1185531 | Using where | 
+----+-------------+----------+-------+---------------+------------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

(是的,由于某种原因,行估计值大于表中的行数。)

所以,显然,这个指数没有意义。

真的没有更好的办法吗?我尝试将列作为时间戳,但结果却变慢了。

编辑:我发现更改查询以使用间隔而不是特定日期最终使用索引,将行估计值减少到上述查询的大约20%:

SELECT kind, COUNT(id) FROM counters WHERE created_at >= 
    (NOW() - INTERVAL 7 DAY) GROUP BY kind;

我不完全确定为什么会发生这种情况,但我相信如果我明白这一点,那么问题通常会更有意义。

2 个答案:

答案 0 :(得分:0)

在阅读了关于该问题的最新编辑之后,问题似乎是WHERE子句中使用的参数被MySQL解释为字符串而不是datetime值。这可以解释为什么优化器没有选择index_counters_on_created_at索引,而是导致扫描将created_at值转换为字符串表示,然后进行比较。我认为,可以通过datetime子句中的where显式强制转换来阻止这种情况:

where `created_at` >= convert({specific_date}, datetime)

我原来的评论仍然适用于优化部分。

这里真正的性能杀手是kind列。因为在执行GROUP BY数据库引擎时,首先需要确定kind列中导致表或索引扫描的所有不同值。这就是为什么估计的行大于表中的总行数,在一次通过中它将确定kind列中的不同值,在第二次通过中它将确定哪些行符合{{1条件。 更糟糕的是,create_at >= ?列是一个kind,它太大而无法提高效率,添加它使用varchar (255)字符集和utf8整理,这会增加确定该列中唯一值所需的比较的复杂性。

如果您将utf8_unicode_ci列的类型更改为kind,则效果会更好。因为整数比较比unicode字符比较更有效,更简单。为intkind存储的kind_id条消息提供目录表也很有帮助。然后对类型目录表的连接进行分组,并按日期首先过滤日志表的子查询:

description

这将首先按select k.kind_id, count(*) from kind_catalog k inner join ( select kind_id from counters where create_at >= ? ) c on k.kind_id = c.kind_id group by k.kind_id 过滤counters表,并可以从该列的索引中受益。然后它会将它连接到create_at >= ?表,如果SQL优化器是好的,它将扫描较小的kind_catalog表以进行分组,而不是kind_catalog表。

答案 1 :(得分:0)

为什么不使用连锁索引?

CREATE INDEX idx_counters_created_kind ON counters(created_at, kind);

应该进行仅索引扫描(在Extras中提及“使用索引”,因为COUNT(ID)无论如何都是NULL)。

参考文献: