组合同一个表中的统计信息查询

时间:2015-09-18 20:22:18

标签: mysql optimization

我最近在SO上看到了将来自同一history表的三个查询合并为一个以提高性能的请求。

三个查询是

SELECT COUNT(*) as number, SUM(order_total) as sum FROM history;
SELECT COUNT(*) as number, SUM(order_total) as sum FROM history 
    WHERE date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY));
SELECT COUNT(*) as number, SUM(order_total) as sum FROM history
    WHERE date <= UNIX_TIMESTAMP(CURDATE());

所以我认为我会格式化一个更一般的问题,以上为例:如何组合更多的查询,以及如何最好地进行?

1 个答案:

答案 0 :(得分:1)

所有查询都访问相同的变量,并且仅用于运行总和和总计的条件不同。

要在一个查询中全部运行,我们必须将每个结果分配到不同的列,因此我们不会numbersum而是number1 },number2,... sum3,以便访问结果。

基本更换

通常,COUNT()SUM()等是aggregate functions,因此我们将使用包含该条件的新表达式替换每个实例。

例如:COUNT(*) WHERE some_condition

相同
add 1 for each record among the records where <some_condition>

可以改写(尽管有点慢)

add 1 if <some_condition>, else 0, for each record among ALL the records

SUM(IF(<some_condition>, 1, 0))

同样适用于SUM(value) WHERE <some_condition>:它变为SUM(IF(<some_condition>, value, 0))

在考虑MIN()MAX()AVG()时,我们发现默认值0可能会有问题。使用NULL而不是0来解决此问题。

我们的第一次迭代允许简单的替换:

Single query                 Combined query
COUNT(*)                     SUM(<conditionalOne>)
SUM(value)                   SUM(<conditionalValue>)
AVG(value)                   AVG(<conditionalValue>)
MIN(value)                   MIN(<conditionalValue>)
...

<conditionalValue>,如果<condition>存在,

IF(<condition>, value, NULL)

,只是value<conditionalOne><conditionalValue>,其中值等于1.否则,value可以是字段名称或表达式。

所以我们的示例查询变为:

SELECT
    SUM(1) AS number1, SUM(order_total) AS sum1,
    SUM(IF(date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY)), 1, NULL)) AS number2,
    SUM(IF(date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY)), order_total, NULL)) AS sum2,
    SUM(IF(date <= UNIX_TIMESTAMP(CURDATE()), 1, NULL)) AS number3,
    SUM(IF(date <= UNIX_TIMESTAMP(CURDATE()), order_total, NULL)) AS sum3
FROM history;

合并WHEREs

在这种情况下,至少有一个条件对整个表有效,即一个查询没有WHERE;所以我们需要扫描整个表格。那么我们也可以完全没有WHERE

否则我们会合并这三个条件并使用它们中最大或最宽松的(所以如果我们选择去年,上个月和上周,我们实际上只会添加去年的选择)。

我们可以自动执行此操作,并希望MySQL优化器能够解决问题:

WHERE (<condition1>) OR (<condition2>) OR (<condition3>);

索引优化

由于索引,很可能单个查询实际上会比几个脱节查询运行更慢。如果条件和值实际上针对几个不同的列,通常会发生这种情况,从而使索引效率降低。

如果根本没有索引,那么合并查询应该总是比单独运行它们更方便。

理论上,我们希望covering index包含WHERE子句中出现的所有列,从具有最小基数的列到具有最大基数的列,然后是表达式中出现的所有列。这样,MySQL选择器将快速置零所需的行,并且还将在内存中找到所需的值。

在此示例中,条件基于date,查询要求order_total,因此我们只使用两列创建索引。

 CREATE INDEX history_stat_ndx ON history(`date`, order_total);
然而,在实践中,覆盖指数可能太大而无法被接受,或者如果它是有益的。在这种情况下,我们仍然会合并多个查询,但这次是多个查询:

  • 需要全表扫描和/或大量列的查询,特别是如果其他查询不需要相同的查询,它将自行完成,并将与具有相同特征的所有其他查询合并,并且没有被编入索引(我们从索引中获得的收益很少。不适用于WHERE,因为有一个完整的表扫描,而不是来自覆盖,因为那里的列太多了。)

    < / LI>
  • 表达式中需要类似条件或类似列的所有查询可以组合在一起,如果条件确实相似,则可能编入索引。每个组可能具有不同的索引,针对该组及其表达式进行了优化。