使用以相同字段开头的 GROUP BY 优化多个查询

Posted

技术标签:

【中文标题】使用以相同字段开头的 GROUP BY 优化多个查询【英文标题】:Optimizing several queries with GROUP BY that starts with the same fields 【发布时间】:2015-10-28 09:43:33 【问题描述】:

数据库是 mysql。例如:我有一个表和几个使用 GROUP BY 的 SELECT 查询:

SELECT
    MIN(price)
FROM `table`
GROUP BY
    field1, field2, field3, field4;

SELECT
    MIN(price)
FROM `table`
GROUP BY
    field1, field2, field3, field5;

SELECT
    MIN(price)
FROM `table`
GROUP BY
    field1, field2, field3, field6;

所有查询都按 field1、field2、field3 进行分组。有没有办法优化或缓存相同的操作?

【问题讨论】:

但它们都包含额外的列,这使得无法达到你想要做的事情 发布您的 table 架构和存储的数据样本,我想您应该重新设计表格 【参考方案1】:

您尝试做的事情听起来很简单,但实际上只有从“代码保护”的角度来看才有意义(这意味着更少的代码,而不是真正减少数据库的工作量)。实际上,“GROUP BY field1, field2, field3”实际上是“GROUP BY field1, field2, field3, field4”返回的子集。让我用一个数据集来说明:

price | field1 | field2 | field3 | field4
------|--------|--------|--------|-------
1.00  |    1   |   1    |   1    |   1   
1.50  |    1   |   1    |   1    |   2   
2.00  |    1   |   1    |   2    |   3   
3.00  |    1   |   1    |   2    |   3   

"GROUP BY field1, field2, field3" 返回:

min_price | field1 | field2 | field3 
----------|--------|--------|--------
   1.00   |    1   |   1    |   1    
   2.00   |    1   |   1    |   2    

"GROUP BY field1, field2, field3, field4" 返回更多行:

min_price | field1 | field2 | field3 | field4
----------|--------|--------|--------|-------
   1.00   |    1   |   1    |   1    |   1   
   1.50   |    1   |   1    |   1    |   2   
   2.00   |    1   |   1    |   2    |   3   

如您所见,您无法以某种方式重用第一个 group by 语句来获得第二个结果集。

如果性能是您的问题,@deadzone 对物化视图的建议是一个很好的建议。您可以在所有个字段的分组上创建一个物化视图,如果它会配对一些行(GROUP BY field1、field2、field3、field4、field5、field6)。除此之外,您只需要确保每个查询都经过优化。

如果您关心代码保存,MySQL 不会为您提供很多选择,因为它不支持动态 SQL。 SQL 是一种语言,最好的选择往往是编写更多代码(插入内部程序员 sigh)。

【讨论】:

【参考方案2】:

如果您希望提高使用聚合数据的选择查询的性能,我建议您查看一个或多个Materialized Views。这(在幕后)有点类似于拥有额外的表格。但它们是源表上的视图,需要定期刷新。尽管创建/刷新 MV 的速度可能不是很快,但查询它们以获取这些查询应该会提供显着的性能提升。

【讨论】:

【参考方案3】:

想到了两种方法:

1) 使用temporary table。将所有 6 个字段分组,并将中间结果保存在临时表中,然后再分组 3 次得到最终结果。

CREATE TEMPORARY TABLE temp_tbl_name
SELECT
    field1, field2, field3, field4, field5, field6
    ,MIN(price) AS price
FROM table
GROUP BY
    field1, field2, field3, field4, field5, field6;


SELECT
    MIN(price)
FROM temp_tbl_name
GROUP BY
    field1, field2, field3, field4;

SELECT
    MIN(price)
FROM temp_tbl_name
GROUP BY
    field1, field2, field3, field5;

SELECT
    MIN(price)
FROM temp_tbl_name
GROUP BY
    field1, field2, field3, field6;


-- temp table would be dropped automatically, so often explicit DROP is not needed
DROP TABLE temp_tbl_name;

一个 TEMPORARY 表只对当前会话可见,并且是 会话关闭时自动删除。

如果所有 6 个字段的第一次聚合显着减少了行数,则此方法是有意义的。

2) 考虑使用GROUP BY WITH ROLLUP

SELECT
    field1, field2, field3, field4, field5, field6
    ,MIN(price)
FROM table
GROUP BY
    field1, field2, field3, field4, field5, field6
WITH ROLLUP;

这个单一的查询将产生分组字段的所有变体,而不仅仅是您所追求的三个,因此您需要进一步过滤结果。

【讨论】:

【参考方案4】:

tl;博士:

create index cov_index on table (field1, field2, field3, field4, field5, field6, price);

说明

没有太多方法可以缩减在这些查询之间共享工作所需的代码。

但是有一种方法可以使查询更有效率。它被称为covering index。它是一个索引,包含 MySQL 查询引擎满足您的查询所需的所有列。

为了优化您的第一个查询,我们需要索引中的这些列。

  field1, field2, field3, field4, price

MySQL 中使用的 BTREE 样式索引本质上是按顺序排序的。因此,查询引擎可以通过遍历索引来满足这个MAX(price) ... GROUP BY all the rest 查询,以便执行称为loose index scan 的操作。这是因为索引包含,按顺序GROUP BY 中提到的所有列,然后是被汇总的列。松散索引扫描速度惊人。

但您还需要GROUP BY field1, field2, field3, field5 和查询的其他一些变体。如果您希望所有变体查询都满足松散的索引扫描,那么您需要为每个查询创建一个单独的覆盖索引。有很多索引。

但是如果您愿意通过完整索引扫描而不是松散索引扫描来满足某些查询,您可以在覆盖索引中放置更多列,因此所有字段都被提及。前三个字段需要保持顺序。

field1, field2, field3, field4, field5, field6, price

是您需要的索引。您的第一个查询仍然可以使用松散索引扫描。其余的仍将利用索引的部分排序。

当然,索引有一个缺点:它们会减慢插入和更新速度。

【讨论】:

field1, field2, field3, field4, field5, field6, price 上的索引如果查询具有 GROUP BY field1, field2, field3, field5,则不适用,因为这些列不是最左边的前缀。 @LFJ 索引仍然可以用于严格的索引扫描以满足查询。没错,它不适用于松散的索引扫描。【参考方案5】:

我认为处理GROUP BY 的最有效方法是使用索引直接检索分组列。

如果您的查询是针对单个表,并且选择中仅使用了 MIN() 或 MAX() 聚合函数,则最好创建一个多列索引。

试试这个:

create index index_name1 on your_table(field1, field2, field3, field4);
create index index_name2 on your_table(field1, field2, field3, field5);
create index index_name3 on your_table(field1, field2, field3, field6);

GROUP BY Optimization

【讨论】:

【参考方案6】:

您可以在 SUBQUERY 中使用 GROUP BY 字段 1、2、3 来最小化结果集。

设置 SUBQUERY Chache;

SET optimizer_switch='subquery_cache=on';

SELECT
    id,
    MIN(price)
FROM (
    SELECT
        id,
        MIN(price)
    FROM `table`
    GROUP BY
        field1, field2, field3
) AS tmp
GROUP BY
    field1, field2, field3, field4;


SELECT
    id,
    MIN(price)
FROM (
    SELECT
        id,
        MIN(price)
    FROM `table`
    GROUP BY
        field1, field2, field3
) AS tmp
GROUP BY
    field1, field2, field3, field5;

【讨论】:

这并没有真正最小化结果集,因为您还必须按 id 分组。缓存子查询可能会使它更快一点... @Daniel Grosskopf 抱歉,问题大战:所有查询都按 field1、field2、field3 进行分组。有没有办法优化或缓存相同的操作? 这个查询永远不会运行。如果tmp 派生表没有返回,你将如何在外部查询中按field5 分组? 你说得对。内部查询必须提供您在外部查询中拒绝的所有字段。对不起

以上是关于使用以相同字段开头的 GROUP BY 优化多个查询的主要内容,如果未能解决你的问题,请参考以下文章

mysql group by 对多个字段进行分组

sql查询中如何用group by查询出完整的一行记录?

用group by语句时,字段很多并且数据量也很大的情况如何解决?

group by...having count()的问题

JPA Group by 具有多个字段

GROUP BY 优化,包含 OR 条件的分组规则