MySQL 日期范围查询优化

MySQL Date Range Query Optimization

我有一个 MySQL table 结构如下:

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 图的日期范围查询此 table 的各种统计信息,但是所有这些查询都非常慢,尽管 table 使用 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 多秒才能完成 table 中的 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确实是在使用我创建的复合主键来索引数据,但仍然要扫描几乎整个table,我不明白。我如何针对日期范围查询优化此 table?

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

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

方案A:

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

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

B计划:

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

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

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

方案 C:可能更好的方法:

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

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

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