【问题标题】:Optimizing Datetime fields where indexes aren't being used as expected优化未按预期使用索引的日期时间字段
【发布时间】:2011-12-11 07:22:47
【问题描述】:

我在运行 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;

我不完全确定为什么会发生这种情况,但我相当有信心,如果我理解了它,那么这个问题通常会更有意义。

【问题讨论】:

  • 我认为,发生的事情是当您使用特定日期时将其作为字符串输入,这导致 MySQL 将 created_at 转换为字符串而不使用索引。你能试试WHERE created_at >= CONVERT(DATETIME, '{specific date}')之类的吗?
  • 使用(例如)CONVERT('2011-10-13', DATETIME) 时没有变化。

标签: mysql performance datetime indexing database-performance


【解决方案1】:

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

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

我原来的cmets还在申请优化部分。

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

如果您将kind 列的类型更改为int,这将执行得更好。因为整数比较比 unicode 字符比较更高效、更简单。为存储kind_iddescription 的消息的kind 建立一个目录表也会有所帮助。然后对类别目录表的连接和首先按日期过滤的日志表的子查询进行分组:

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

这将首先按create_at >= ? 过滤counters 表,并且可以从该列的索引中受益。然后它将它加入到kind_catalog 表中,如果SQL 优化器很好,它将扫描较小的kind_catalog 表来进行分组,而不是counters 表。

【讨论】:

  • 在不涉及种类字段(且不分组)的情况下尝试相同的查询会产生与 EXPLAIN 相同的结果。 Kind 本身也被索引(有 99 个不同的值)。
  • 您尝试的查询是什么?如果您查看解释输出,使用的密钥是index_counters_on_kind。另请注意,count(*) 可能比 count(id) 更有效。
  • 这里尝试的确切查询:select kind, count(*) from counters where created_at >= CONVERT('2011-10-13', DATETIME) group by kind;
  • 还尝试了这个来消除作为瓶颈的种类:select count(*) from counters where created_at >= CONVERT('2011-10-13', DATETIME);
【解决方案2】:

为什么不使用级联索引?

CREATE INDEX idx_counters_created_kind ON counters(created_at, kind);

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

参考资料:

【讨论】:

  • 刚刚试了一下,但最终得到了相同的结果——与未索引版本的估计值相同。
猜你喜欢
  • 1970-01-01
  • 2011-02-20
  • 2010-10-17
  • 2019-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
相关资源
最近更新 更多