【问题标题】:MYSQL GROUP BY and WHERE indexes, with timestamp columnMYSQL GROUP BY 和 WHERE 索引,带有时间戳列
【发布时间】:2019-03-24 14:15:20
【问题描述】:

我已经实现了这个查询:

SELECT 
    evt.userId, evt.storeId, COUNT(1) AS totalVisits
FROM
    Event evt
WHERE
    evt.timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW()
    AND 
    evt.subtype = 2 
    AND 
    userID IS NOT NULL
GROUP BY userId, storeId
HAVING totalVisits>16;

Event 表有数百万条记录。列时间戳为 DATETIME,其他列为 INT。这张表被频繁访问并且有很多索引。

一开始,这个查询的执行时间超过了 10 分钟。我通过添加一个新索引来解决这个问题

ALTER TABLE Event 
    ADD INDEX `Event_timestamp_subtype_userId_storeId` (`timestamp` ASC, `subType` ASC, `userId` ASC, `storeId` ASC);

这很好,我在不到 2 秒的时间内就有了结果。

我遇到的问题是当我更改条件 INTERVAL 30 DAY 时。如果我设置 INTERVAL 50 DAY(例如),MYSQL 不会使用我创建的索引。相反,它使用另一个仅涵盖两列的索引。

解释命令:

EXPLAIN EXTENDED SELECT 
    evt.userId, evt.storeId, COUNT(1) AS totalVisits
FROM
    Event evt
WHERE
    evt.timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 50 DAY) AND NOW()
    AND 
    evt.subtype = 2 
    AND 
    evt.userID IS NOT NULL
GROUP BY userId, storeId
HAVING totalVisits>16;

解释输出:

+----+-------------+-------+------------+------+------------------------------------------------------------------------------------------------------------+-----------------------------+---------+-------+---------+----------+---------------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys                                                                                              | key                         | key_len | ref   | rows    | filtered | Extra                                                               |
+----+-------------+-------+------------+------+------------------------------------------------------------------------------------------------------------+-----------------------------+---------+-------+---------+----------+---------------------------------------------------------------------+
|  1 | SIMPLE      | evt   | NULL       | ref  | Event_userId_index,Event_subType_storeId_index,Event_timetamp_index,Event_timestamp_subtype_userId_storeId | Event_subType_storeId_index | 3       | const | 7375964 |    25.00 | Using index condition; Using where; Using temporary; Using filesort |
+----+-------------+-------+------------+------+------------------------------------------------------------------------------------------------------------+-----------------------------+---------+-------+---------+----------+---------------------------------------------------------------------+

因此,如果设置 50 天条件,则查询是不可行的。如何使此查询与参数值无关地使用正确的索引?

我正在使用 mysql 服务器 5.7.23

谢谢!

问候

【问题讨论】:

  • 您在dba.stackexchange.com 上提出这个问题可能会更幸运但是,您是否尝试过在定义索引时更改列的顺序? MySQL 可能决定您的索引没有足够的选择性,因为该范围内有大量时间戳。如果你把例如user_id 或 store_id 首先它可能会更好。
  • 就个人而言,我会尝试一个避免“使用文件排序”操作的执行计划,并在(subtype,userid,storeid,timestamp) 上使用覆盖索引(我可能会将查询修改为GROUP BY subtype,userid,storeid

标签: mysql indexing group-by


【解决方案1】:

你有“很多索引”。霰弹枪没有帮助。你有这些索引,列在给定的顺序

INDEX(subtype, timestamp)
INDEX(subtype, userID)

优化器可能希望将其中任何一个用于WHERE。而且,由于它不能消耗所有 WHERE(因为有 2 个范围),它不会到达 GROUP BY 中的列。

第一列(subtype)用=测试;这很容易。
第二列是一个“范围”,所以这是它可以处理的最后一件事。

将这些索引中的每一个都变成“覆盖”索引可以有一个小的改进:

INDEX(subtype, timestamp, storeID, userID)
INDEX(subtype, userID, timestamp, storeID)

现在处理只需要查看索引的 BTree,而不必在 BTree 和有数据的 BTree 之间跳转。

(前两列按特定顺序排列;其他两列可以互换。)

如果这是一个“巨大”表(数百万行),我们可以讨论另一种优化,因为您实际上需要一个 2D 索引。

【讨论】:

  • 我用覆盖索引解决了这个问题......但是字段的顺序是子类型,storeId,userId,时间戳。我测试了其他组合,但这个组合是最好的。查询在不到 1 秒的时间内运行,没有文件排序或昂贵的操作。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-18
  • 1970-01-01
  • 2013-01-07
  • 1970-01-01
  • 1970-01-01
  • 2020-10-29
  • 1970-01-01
相关资源
最近更新 更多