【问题标题】:Optimizing MySQL query using GROUP BY on time functions使用 GROUP BY 时间函数优化 MySQL 查询
【发布时间】:2011-05-29 05:33:26
【问题描述】:

我有以下疑问:

SELECT location, step, COUNT(*), AVG(foo), YEAR(start), MONTH(start), DAY(start)
FROM table WHERE jobid = 'xxx' AND start BETWEEEN '2010-01-01' AND '2010-01-08'
GROUP BY location, step, YEAR(start), MONTH(start), DAY(start)

最初我在各个列上都有索引,例如 jobidstart,但很快意识到 MySQL 在 select 中只支持每个表的一个索引。因此,它将使用 jobid 索引,然后进行相当大的扫描以按 start 范围过滤掉。

在 (jobid, start) 上添加索引有很大帮助,但 GROUP BY 仍然会导致性能问题。我已阅读docs on GROUP BY optimizations 并了解为了从这些优化中受益,我需要一个包含 (location, step, start),但我还有两个悬而未决的问题:

  1. group by 优化是否可以使用时间函数(YEAR、MONTH、DAY 等)?还是我必须将这些值存储为单独的列?我喜欢执行这些功能的原因是,这意味着我可以在每个连接的基础上控制时区,并返回为最终用户时区量身定制的结果。如果我必须预先存储年、月和日,我会通过 UTC 来完成,然后我的所有用户都只会收到 UTC 格式的报告。

  2. 即使我可以解决问题 #1,我还能做到吗?索引 (jobid, start) 有助于 WHERE 子句,但 GROUP BY 需要优化不同的索引 (location, step, start) 或者,取决于对#1 的回答,(location, step, year、)。但问题是这两个索引不共享一个共同的左侧列集,所以我不相信我的 WHERE 和 GROUP by 可以兼容以便使用相同的索引。所以我的问题是:我只是在这里冲洗吗?

关于如何实现这一点的任何其他想法都会有所帮助。而且,只是为了抢占一些可能出现的问题/cmets:

  1. 是的,这是一个时间序列数据集。
  2. 是的,它会从 RRDtool 之类的东西中受益,但这样做会导致我失去执行特定时区的结果。
  3. 是的,预先计算汇总可能是个好主意,但我不需要 令人敬畏的 性能,因此如果它允许,我对 良好 性能感到满意我为每个用户的时区自定义结果。

如上所述,如果有人对如何执行汇总或循环数据库之类的操作有任何设计建议,但仍能获得特定时区的结果,我会全力以赴!


更新:根据要求,这里有更多信息:

显示输出中的索引:

步骤 0 PRIMARY 1 step_id A 16 NULL NULL BTREE 步骤 1 开始 1 开始 A 16 NULL NULL BTREE 步骤 1 步骤 1 步骤 A 2 NULL NULL BTREE 步骤 1 foo 1 foo A 16 NULL NULL YES BTREE 步骤 1 位置 1 位置 A 2 NULL NULL YES BTREE 步骤 1 jobid 1 jobid A 2 NULL NULL YES BTREE

显示创建表输出:

创建表`步骤`( `start` 时间戳 NOT NULL DEFAULT '0000-00-00 00:00:00', `step` smallint(2) unsigned NOT NULL, `step_id` int(8) unsigned NOT NULL AUTO_INCREMENT, `location` varchar(12) 默认为空, `jobid` varchar(37) 默认为空, 主键(`step_id`), KEY `start_time` (`start`), KEY `step` (`step`), KEY `location` (`location`), KEY `job_id`(`jobid`) ) 引擎=InnoDB AUTO_INCREMENT=240 默认字符集=utf8

【问题讨论】:

  • 您能否发布以下信息:显示 中的索引并显示创建表 - thx
  • @Scrum - 结果集并不大,但它正在增长。我们有许多数据库(每个客户一个),根据客户的使用情况,记录从 10K 到 ~10M 不等。
  • 分组后返回多少行?位置、步骤有多少种不同的组合?
  • @Scrum - 有固定数量的 7 个位置,通常不超过 10-15 个唯一步骤值。一个典型的查询是尝试返回 7 天内每天的唯一位置 + 步骤结果。所以这将是 7 天 x 7 个位置 x 10 步 = 分组后返回 490 行。

标签: mysql database-design optimization query-optimization


【解决方案1】:

并了解为了从这些优化中受益,我需要一个包含(位置、步骤、开始)的索引

不。您可以创建复合索引 jobid + start + location + step,如果没有 BETWEEN,它有所帮助。由于您在 WHERE 中使用范围条件 - 没有索引将用于 GROUP BY 并且您可以为此查询做的唯一也是最好的事情就是 jobid + start 索引。

恕我直言,最好的解决方案是将此表分解为某种预先计算的形式。例如:按调度程序每小时聚合数据。

【讨论】:

  • 两个后续问题:1) 使用 > 和
  • @patrick 1) 否和 2) 因为您使用范围过滤星星,正如 zerkms 所说,它无济于事
  • @Scrum - 不确定“因为您使用范围过滤星号”是什么意思:(
  • @patrick 因为您正在过滤具有一系列值的“开始”列,所以它不能使用该索引进行分组
【解决方案2】:

jobid, start, location, step 上创建单个复合索引

然后首先按该顺序分组,然后对其进行排序:

SELECT location, step, COUNT(*), AVG(foo), YEAR(start), MONTH(start), DAY(start)
FROM table WHERE jobid = 'xxx' AND start BETWEEEN '2010-01-01' AND '2010-01-08'
GROUP BY YEAR(start), MONTH(start), DAY(start), location, step
ORDER BY location, step, YEAR(start), MONTH(start), DAY(start)

更新

当使用 YEAR、MONTH 和 DAY 函数时,MySql 似乎无法使用索引。因为

  1. 从 WHERE 子句中删除 start 后,解释仍然显示 using filesort
  2. 添加 3 列:y = YEAR(start), m = MONTH(start), d=DAY(start),在 jobid, y, m, d, location, step 上创建索引并更新 WHERE ... AND y = 2010 AND m = 12 AND d BETWEEN 1 AND 08 会删除 using temporary using filesort

保留 3 个额外的列似乎是个坏主意,因为 GROUP BY 之间的性能差异不应该有那么重要,如果它使用临时或不使用。

【讨论】:

  • 在分组之后应用排序。所以没有理由在start 之后添加location。另外,我是否指出,在范围类型条件之后不会使用复合索引部分。
  • 我在一个有 200K 条目的表上添加了这个索引,这个查询和我的原始查询的解释计划看起来一样。这是否意味着它们是相等的,还是解释计划中没有描述其他优化?
  • @Patrick 如果删除 AVG(Foo) 列会怎样? (由于 Foo 不在您的问题中的解释表输出中,因此我忽略了它)
  • @Scrum - 相同的解释计划:使用新索引、输入“范围”、17970 行和“使用位置;使用临时的;使用文件排序的附加功能。返回的实际行数为 98(7 个位置、2 个步骤、7 天)。
  • @zerkms 你不正确,因为从 WHERE 子句中删除起始范围没有帮助。
【解决方案3】:

改为这样做

GROUP BY location, step, YEAR(start), MONTH(start), DAY(start)
ORDER BY location, step, YEAR(start), MONTH(start), DAY(start)

试试

GROUP BY location, step, date_format(start, '%Y%m%d')
ORDER BY location, step, date_format(start, '%Y%m%d')

【讨论】:

  • 我考虑过使用 date_format,但我真的没有任何充分的理由说明它会显着改变事情。你能告诉我为什么你认为这会有所帮助吗?
  • 你所做的将在start上使用3个函数,这种方法只使用一个函数并返回相同的结果
  • 我不认为函数调用是我的主要性能瓶颈。我想要完成的是优化组,使其仅使用索引,而不需要使用更大的数据集做一个临时表。
  • 按 5 个字段分组与 3 个字段相比,什么给您更好的提升?在索引旁边,您可以考虑有一个列start_date date,它是从列开始的日期派生的,并将范围更改为IN,在jobid,start,location,step 上构建复合索引,就像@zerkms 建议的那样
【解决方案4】:

如果位置和步长是整数外,这可能会更快地选择 键进入其他只有名称和整数 id 的表。

首先,查询将按整数数据分组,这样比较起来会快很多。 其次,数据库引擎可能会自动索引这些数字。

我还考虑将 jobid 卸载到单独的表中,以防值重复。

【讨论】:

    猜你喜欢
    • 2011-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 2019-08-16
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    相关资源
    最近更新 更多