sum / case查询的最佳索引策略

时间:2013-07-16 20:47:31

标签: mysql performance indexing innodb b-tree

我有一个带有innodb的MySQL数据库作为存储引擎,我有许多基本形式的查询:

SELECT bd.billing,
  SUM(CASE WHEN tc.transaction_class = 'c'  THEN bd.amount ELSE 0 END) AS charges,
  SUM(CASE WHEN tc.transaction_class = 'a' THEN bd.amount ELSE 0 END) AS adjustments,
  SUM(CASE WHEN tc.transaction_class = 'p' THEN bd.amount ELSE 0 END) AS payments,
  SUM(bd.amount) AS balance_this_month
FROM billing_details bd
JOIN transaction_classes tc ON tc.transaction_code = bd.transaction_code
WHERE bd.entry_date BETWEEN '2013-06-04' AND '2013-07-01'
GROUP BY billing;

我正在尝试制定最佳策略,以便为采用此表单的查询索引列。在我开始之前,单列上只有索引,并且解释显示正在读取1.5M行(正如您在此处所看到的,只有一个月的数据量)。

我的第一次尝试将此数字降至约300,000,这是通过索引(entry_date,billing,transaction_code)实现的。在做了一些更多的阅读(特别是高性能MySQL)后,我决定使用entry_date(通常是一个范围表达式)作为我最左边的列不是最佳的,所以我尝试了(billing,transaction_code,entry_date)并解释了更像是4的东西-500,000行。仍然是第一个数字的改进,但随着我深入挖掘,我开始怀疑:

我可以合理地期望从这种查询的最佳索引中得到什么?我猜测,因为我正在执行一个聚合函数,它总是要构建一个临时表并执行一个文件...或者是它?我读的越多,我就越困惑。我的直觉是使用entry_date作为最左边的列,因为它是我的where子句中唯一的规定。更多的研究使我相信我应该把它放在最正确的位置,因为我正在查询一系列日期。但那时我所读到的只是真正谈到了where子句 - 它只有entry_date:那么一个sum / case查询怎么样呢?我是否可以通过有益的方式为此索引添加数量,或者除非我重新设计架构/查询,否则我将无法使用我所拥有的内容?

1 个答案:

答案 0 :(得分:1)

从您的查询中,我们不清楚非限定列(例如entry_date)引用哪个表格。 (最佳做法是在查询中限定所有列引用,以便读者获益,并且当将同名列添加到其他表中时,将来自"模糊列"异常的查询。查询。)

我将假设不合格的列来自billing_details表。

覆盖索引的最可能的候选者是:

... ON billing_details (entry_date, billing, transaction_code, amount)

... ON transaction_classes (transaction_code, transaction_class)

EXPLAIN应显示"使用索引"在extra列中,用于两个表访问。 (如果transaction_classes表足够小,索引可能根本不重要。)

A"覆盖索引"意味着可以完全从索引中满足查询,而无需引用基础表的页面。

Optimizing Queries with EXPLAIN http://dev.mysql.com/doc/refman/5.5/en/using-explain.html

此处的策略是首先在索引中获取谓词中的列,因此可以执行索引范围扫描操作。我认为其他列的顺序不太重要。接下来的计费栏可以帮助MySQL与GROUP BY,但我认为测试将揭示它并不重要。

JOIN操作可能会受益于连接谓词中的列的索引,在这种情况下,可以受益于较小的transaction_classes" lookup"表。但是,如果内部联接实际上是从billing_details表中过滤出行(在transaction_classes表中没有匹配值的行,那么我们可能会将其视为过滤谓词,并且有一个索引。我怀疑但是,存在外键关系,并且在billing_details表中此列为NOT NULL,这样billing_details表中的每一行都在transaction_classes表中具有匹配的行。

如果正在访问billing_details表中的大多数行,则首先在GROUP BY中引用列而不是谓词中的列可能是有益的,例如:

... ON billing_details (billing, entry_date, transaction_code, amount)

在这种情况下,MySQL可以避免使用"使用文件排序"将行组合在一起的操作。同样,我不认为在那之后其他列的顺序很重要。在这种情况下,它将是全索引扫描,而不是范围扫描。索引中的每一行都需要检查entry_date,以确定它是否包含在内。

如果entry_date上的谓词返回一小部分(例如,少于10%)的行,则首先使用该列的索引的访问计划可能会更好。


<强>摘要

就此查询的性能而言,获取谓词的索引可以显着减少识别要包含的行所需的工作量,而无需访问每一行。

下一个&#34;大摇滚&#34;是GROUP BY。如果您访问表中的每一行(根本没有谓词),则最佳索引位于GROUP BY子句中的列上。因为这些值是按此列排序的,所以MySQL可以避免执行排序操作,这在大型集合上可能很昂贵。

除了billing_details表上的适当索引之外,您可以做的下一件事就是取消对transaction_classes表的连接,并仅使用transaction_code列中的值。

CASE中条件的处理对查询时间没有显着贡献。花费时间的是访问需要处理的值,并对行进行排序,以便它们可以&#34;分组&#34;。


<强>跟进

&#39;使用临时;使用filesort&#39;在计划中是由于GROUP BY操作。 MySQL使用WHERE子句的索引来减少行数。现在MySQL必须采取这些行并对它们进行排序。这是预期的。

至少使用&#39;使用索引&#39;表明MySQL完全从索引获取行,无法访问基础表(这通常会提升性能。)

唯一可以避免&#34;使用filesort&#34; GROUP BY(AFAIK)是一个索引,其中GROUP BY中引用的列为前导列。

要查看MySQL是否会使用这样的索引,您可以尝试禁用MySQL使用WHERE子句的索引的能力。执行此操作(用于测试)的最简单方法是在函数的WHERE子句中包装bd.entry_date列引用。

更改该谓词,并使用其中一些变体尝试EXPLAIN

WHERE DATE(bd.entry_date) BETWEEN 
WHERE DATE(bd.entry_date) + INTERVAL 0 DAY BETWEEN
WHERE DATE_FORMAT(bd.entry_date,'%Y-%m-%d') BETWEEN

其中一些(或全部)应该足以禁止MySQL使用带有entry_date的索引来导致满足WHERE子句。

在有效禁用该索引的情况下,MySQL可能会决定使用billing列作为前导列的索引,以避免使用&#34;使用filesort&#34;操作。 (在这种情况下,索引还包括entry_date列几乎是必要的,因为需要在表中的每一行检查该列,实际上是&#34;完全扫描&#34;所有的行。

对于一小部分行,这个查询计划可能会更加昂贵。这可能会运行得更慢,但它确实需要进行测试。 (如果查询根本没有WHERE子句,并且它正在拉动所有行,那么这种类型的计划(很可能)比执行排序操作快得多。)