如何在 MySQL 中按日期/时间段对结果进行分组,同时仍使用索引?

Xeo*_*oss 2 mysql sql group-by date query-optimization

在 MySQL 中,您可以创建要在查询中使用的索引,以防止全表扫描。只能使用一个索引。

此外,为了使用索引,索引的字段不能通过函数(即DATE(), MONTH(), YEAR())运行,因为这样查询优化器将不知道结果是什么,因此不能使用索引并将回退到而是进行完整(或部分)表扫描。

假设您想要运行一个按日/月/季度/年 ( GROUP BY date(created_at)) 对条目进行分组的报告,您如何设计一个查询来在仍然使用索引的情况下执行此操作?

示例表:

CREATE TABLE `datesort` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `value` int(11) NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `c_v` (`created_at`,`value`)
) ENGINE=InnoDB;

-- Problem Query
EXPLAIN SELECT COUNT(*), `value`, created_at
FROM datesort
WHERE created_at > NOW() - INTERVAL 1 DAY
GROUP BY date(created_at), value;

-- Using where; Using index; Using temporary; Using filesort

vs

EXPLAIN SELECT COUNT(*), `value`, created_at
FROM datesort
WHERE created_at > NOW() - INTERVAL 1 DAY
GROUP BY created_at, value;

-- Using where; Using index 
-- (notice no DATE() in GROUP BY)

Run Code Online (Sandbox Code Playgroud)

请注意,第一个查询必须导致部分表扫描 ( Using temporary; Using filesort),因为c_v由于DATE(created_at).

第二个查询不按日期排序(按秒排序),但可以单独使用索引,而不会导致读取记录数据。

由于按时间段分组在报表中非常常见,因此如何仅使用索引按日/月/季度/年对记录进行分组?

GMB*_*GMB 6

扩展WOUNDEDStevenJonesRick James的有用评论:您可以创建一个生成的列,用于存储每个记录的日期部分(不带时间部分)并为其建立索引。

alter table datesort
    add column date_created_at date
    generated always as (date(created_at)) stored
;

create index myidx on datesort(date_created_at, value);
Run Code Online (Sandbox Code Playgroud)

现在您可以再次尝试查询。为了充分利用索引,您最好需要更改子句where,以便它使用生成的日期列而不是原始的日期时间列(希望这仍然适合您的用例):

select count(*) cnt, value,  date_created_at
from datesort
where date_created_at > current_date - interval 1 day
group by date_created_at, value;
Run Code Online (Sandbox Code Playgroud)

这会产生预期的explain

编号 | 选择类型 | 表| 隔断| 类型 | 可能的键 | 关键| key_len | 参考| 行 | 过滤| 额外的                   
-: | :---------- | :----- | :--------- | :---- | :------------ | :---- | :------ | :--- | ---: | --------:| :------------------------
 1 | 简单| 日期排序 |        | 索引 | myidx | myidx | 8 | | 1 | 100.00 | 使用地点;使用索引

DB Fiddle 上的演示