【问题标题】:MySQL Date Range Query OptimizationMySQL 日期范围查询优化
【发布时间】:2021-03-17 22:04:18
【问题描述】:

我有一个如下结构的 MySQL 表:

CREATE TABLE `messages` (
  `id` int NOT NULL AUTO_INCREMENT,
  `author` varchar(250) COLLATE utf8mb4_unicode_ci NOT NULL,
  `message` varchar(2000) COLLATE utf8mb4_unicode_ci NOT NULL,
  `serverid` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `guildname` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`,`date`)
) ENGINE=InnoDB AUTO_INCREMENT=27769461 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

我需要使用 Grafana 图表的日期范围查询该表以获取各种统计信息,但是所有这些查询都非常慢,尽管该表是使用 id 和日期的复合键索引的。 “id”是自动递增的,日期也一直在递增。

Grafana 生成的查询如下所示:

SELECT
  UNIX_TIMESTAMP(date) DIV 120 * 120 AS "time",
  count(DISTINCT(serverid)) AS "servercount"
FROM messages
WHERE
  date BETWEEN FROM_UNIXTIME(1615930154) AND FROM_UNIXTIME(1616016554)
GROUP BY 1
ORDER BY UNIX_TIMESTAMP(date) DIV 120 * 120

此查询需要 30 多秒才能完成,表中有 2700 万条记录。 在此输出中解释查询结果:

+----+-------------+----------+------------+------+---------------+------+---------+------+----------+----------+-----------------------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows     | filtered | Extra                       |
+----+-------------+----------+------------+------+---------------+------+---------+------+----------+----------+-----------------------------+
|  1 | SIMPLE      | messages | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL | 26952821 |    11.11 | Using where; Using filesort |
+----+-------------+----------+------------+------+---------------+------+---------+------+----------+----------+-----------------------------+

这表明MySQL确实使用了我创建的复合主键来索引数据,但仍然要扫描几乎整个表,我不明白。如何针对日期范围查询优化此表?

【问题讨论】:

  • date 是索引中的第一列吗? (如果你只显示表和索引的 DDL 会更好......)
  • 我将 DDL 编辑到问题中。
  • 当前主键为(id, date)。
  • 嗯,好的,谢谢。所以不是。尝试更改索引以使date 成为其中的第一列,或者在date 上创建单独的索引。
  • 好的,使用 ALTER TABLE 消息添加新索引 ADD INDEX date_id_index(date, id);将查询时间降至 0.45 秒。谢谢你,你是救生员。请添加一个我可以接受的答案。

标签: mysql optimization date-range


【解决方案1】:

A 计划:

PRIMARY KEY(date, id),  -- to cluster by date
INDEX(id) -- needed to keep AUTO_INCREMENT happy

假设表格很大,在 PK 的开头有 date 会将给定日期范围内的行彼此相邻。这会(某种程度上)最小化 I/O。

B计划:

PRIMARY KEY(id),
INDEX(date, serverid)

现在二级索引正是您提供的一个查询所需要的。它针对按日期搜索进行了优化,并且比整个表更小,因此比计划 A 更快(I/O 方面)。

但是,如果您有很多这样的不同查询,添加更多的索引是不切实际的。

C 计划:可能还有更好的方法:

PRIMARY KEY(id),
INDEX(server_id, date)

理论上,它可以通过二级索引检查每个server_id。但我不确定是否存在这样的优化。

计划 D:除了提供独特的 PRIMARY KEY 之外,您还需要 id 吗?如果没有,可能还有其他选择。

【讨论】:

  • id 也用作记录总数的计数器。除此之外,它根本没有使用。
【解决方案2】:

(id, date) 上的索引没有帮助,因为第一个键是 id 而不是 date

你可以
(a) 删除当前索引并改为索引(date, id) -- 当date 位于首位时,这可用于过滤date,而不管以下列如何 -- 或
(b) 只在(date) 上创建一个额外的索引来支持查询。

【讨论】:

  • 如前所述,这在PRIMARY KEY 上不起作用。
猜你喜欢
  • 2023-01-28
  • 1970-01-01
  • 2014-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-07
  • 2011-01-01
相关资源
最近更新 更多