在大型结果集上 mysql 提高分组性能

Improving group by performance in mysql on large result sets

我们有一个很大的 table,我们称它为“数据”,大约有 20 亿行,数据按日期、位置、名称索引。每行都有一个 'score'.

我们还有一个 table,其中包含此 table 中的所有不同日期。

如果我 运行 这样的查询:

SELECT  AVG(score)
    FROM  Data d
    WHERE  d.date IN (
        SELECT  today
            FROM  dates dt
            WHERE  dt.today > '2020-01-01'
              AND  dt.today <  '2020-06-01'
              AND  d.location = 'Location1');

此查询 returns 需要几秒钟。如果我然后 运行 相同的查询,但查找按名称分组的平均分数,查询需要几分钟。即

SELECT  d.name, AVG(score)
    FROM  Data d
    WHERE  d.date IN (
        SELECT  today
            FROM  dates dt
            WHERE  dt.today > '2020-01-01'
              AND  dt.today <  '2020-06-01'
              AND  d.location = 'Location1')
            GROUP BY  .d.name;

distinct name的个数有几十万,有什么技巧可以提高这样的查询速度吗?

首先试试这个。

 CREATE INDEX data_name_score ON Data (location, date, name, score);

这个 compound covering 索引应该可以加速您的查询。对于 table 您的尺码,制作需要一些时间。 (运行 也许是一夜之间?)

为什么这个索引可以提高查询的性能?将索引视为索引所有列中所有值的排序列表。

  1. MySQL 可以 random-access 找到第一个相关行的索引...第一行包含您选择的 locationdate 在你说的范围内。

  2. 然后它可以按顺序遍历索引,根本不需要返回 table 来满足查询。 namescore 在索引中。

  3. 当它遍历索引时,你瞧,索引项的顺序是处理 GROUP BY 的理想顺序。它会遇到值为 aname 的所有 score 值,然后是 b 的所有分数,依此类推。不需要为每个不同的名称设置一行的内部 table。

    请注意,如果您说的是 MAX(score) 而不是 AVG(score),您的查询可以通过 so-called loose index scan. Those are almost miraculously fast, even faster than the tight index scan 来满足,您的查询将使用

其次,像这样简化您的查询。

SELECT d.name, AVG(score) avgscore 
  FROM Data d
 WHERE d.location = 'Location1'
   AND d.date >= '2020-01-01'
   AND d.date < '2020-06-01'
 GROUP BY d.name;

MySQL 应该可以用我建议的 range scan on the index 来满足你的查询。

而且,请注意,许多 single-column 索引通常 对性能有害 除非它们与您必须执行的实际查询相匹配。几个 single-column 索引 等同于 multi-column 索引。

至于为什么没有索引你的查询会很慢,你可以用EXPLAIN得到MySQL来告诉你它到底是如何满足查询的。它可能必须检查您 table 中的大部分 gigarows 以过滤您想要的并生成结果。

  • 缺少右括号。

  • 使用JOIN dates ...,而不是IN ( SELECT ... )

  • 1 月 1 日是故意遗漏的吗?