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
吗?如果没有,可能还有其他选择。
我有一个 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
吗?如果没有,可能还有其他选择。