在大型结果集上 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 您的尺码,制作需要一些时间。 (运行 也许是一夜之间?)
为什么这个索引可以提高查询的性能?将索引视为索引所有列中所有值的排序列表。
MySQL 可以 random-access 找到第一个相关行的索引...第一行包含您选择的 location
和 date
在你说的范围内。
然后它可以按顺序遍历索引,根本不需要返回 table 来满足查询。 name
和 score
在索引中。
当它遍历索引时,你瞧,索引项的顺序是处理 GROUP BY
的理想顺序。它会遇到值为 a
的 name
的所有 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 日是故意遗漏的吗?
我们有一个很大的 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 您的尺码,制作需要一些时间。 (运行 也许是一夜之间?)
为什么这个索引可以提高查询的性能?将索引视为索引所有列中所有值的排序列表。
MySQL 可以 random-access 找到第一个相关行的索引...第一行包含您选择的
location
和date
在你说的范围内。然后它可以按顺序遍历索引,根本不需要返回 table 来满足查询。
name
和score
在索引中。当它遍历索引时,你瞧,索引项的顺序是处理
GROUP BY
的理想顺序。它会遇到值为a
的name
的所有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 日是故意遗漏的吗?