我在运行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;
我不完全确定为什么会发生这种情况,但我相信如果我明白这一点,那么问题通常会更有意义。
答案 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字符比较更有效,更简单。为int
和kind
存储的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)。
参考文献: