如何在MySQL中使用仅索引扫描的前缀索引

时间:2017-11-02 21:37:01

标签: mysql indexing prefix composite-key

想象一下,我有一个(巨大的)表:

category   type  
--------   ----
foo        EC22
foo        EC00
bar        EC00
bar        EDC0
...        ...

type中的前两个字符具有特殊含义,我只对SELECT目的感兴趣。我想使用带type前缀的复合索引,如下所示:category, type(2)

现在我做的时候:

EXPLAIN SELECT category, type FROM table
WHERE category = 'foo'
AND LEFT(type,2) = 'EC'

...它告诉我MySQL是Using index condition;(意味着读取行以重复检查索引)。

我想使用索引值为EC的所有内容,并继续我的仅索引扫描的其余部分。例如。 EXPLAIN告诉我Using index;(没有condition)。无需仔细检查此字段的实际值,因为我只查看前两个字符。有可能实现或强制这个吗?

更新

我可以SET optimizer_switch='index_condition_pushdown=off';然后EXPLAINUsing index lookup;更改为Using where;,并且速度提高约15%。我想我不完全确定这里发生了什么以及如何仅使用索引来查看我的查询。

2 个答案:

答案 0 :(得分:4)

(我同意Spencer的答案;这个答案增加了更多。)

"我只被允许创建指数" - 如果该法令来自管理层,我建议你热身简历。

INDEX(category, type)

并改变

AND LEFT(type,2) = 'EC'

AND type LIKE 'EC%'

是第一级优化。现在它将使用INDEX中的两个字段。并且,假设查询完全符合规定,索引将覆盖"意味着它不需要在索引和数据之间跳转,而是可以在索引BTree中执行整个查询。

第二级优化是查看type是否可以是ENUM,只有1个字节。这使得表和索引每个数十亿字节变小。 (这个建议可能不实用,因为你的"类型"不是典型的,只有少数不同的值,而且没有"前缀"。

至于为什么"使用Where"快了15%... 可能以下内容:

  • 优化工具看到了WHEREINDEX并说了#34; hot-digitty;这是一个非常好的指数;让我用它!"。然后花了很多时间在索引和数据之间反弹。在"使用Where",它会对索引进行处罚并简单地扫描数据 - 要跳过更多行,但不会来回弹跳。 (优化程序没有足够的良好的统计信息来一致地在两者之间进行选择。在您的示例中,微弱的统计数据误导了它。),某些数据和/或index BTrees当时是(或没有)缓存的。再次运行时间;你可能得出不同的结论。 (典型范围:2x。)

"使用索引条件" (又名,ICP = Index Condition Pushdown)也意味着引擎(InnoDB)获取了行并测试了LEFT(type, 2) = 'EC'。在旧版本(ICP之前)中,InnoDB获取了该行,但必须发送它并且#34; up"到#34;处理程序"进行测试。旧的方式减慢了约2倍的速度。但是,正如你所说,必须提取行。获取行是低效率中最重要的部分。

对于1.2B行,缓存(innodb_buffer_pool_size)是否有空间用于所有数据和所有索引?如果数据是400GB,可能不会。你有多少RAM? buffer_pool约占该设置的70%吗?

至于"前缀"索引(type(2)) - 它们几乎没用;你的代码是一个原因的例子。我告诉人们要避免它们。

如果您的types总是4个英文数字/字母,那么从索引中删除(2)只需2.4GB。 可能是您问题的最佳答案。

另一个想法...... MySQL 5.7和MariaDB有"生成/虚拟列"。您可以为LEFT(type,2)创建并为其编制索引。您需要更改查询以引用该新列。该列(如果不是'持久化')将不占用表中的空格;索引将使用新列,并且不会大于现有(category, type(2))。所以,如果我在这一段中所说的都得到了解决,你就可以获得所需的速度,而不会消耗额外的磁盘空间!

答案 1 :(得分:2)

EXPLAIN显示"使用索引"时,这意味着索引是查询的 覆盖 索引。也就是说,可以完全从索引块中满足查询,而无需在基础表块中查找任何行。

再次查看您的查询。并注意它返回type列(SELECT列表中的表达式。)这是整列。并且整个列在索引中

因此,该索引不能成为查询的覆盖索引,因此MySQL无法显示'使用索引'在EXPLAIN输出中(使用该查询和该索引。)

由于它不是查询的覆盖索引,因此MySQL必须对基础数据页进行查找以获取列的值,以便可以返回。

现在,至于是否使用索引检查条件LEFT(type,2) = 'EC',我们需要检查EXPLAIN输出中的key_len

我们可以在key_len列与条件不存在时比较EXPLAIN中的type。我也会使用type LIKE 'EC%'等条件进行测试。

我将从EXPLAIN中比较key_len所有这些:

 SELECT category, type FROM huge_table WHERE category = 'bar' ; 
 SELECT category, type FROM huge_table WHERE category = 'bar' AND type LIKE 'E%' ; 
 SELECT category, type FROM huge_table WHERE category = 'bar' AND type LIKE 'EC%' ;
 SELECT category, type FROM huge_table WHERE category = 'bar' AND LEFT(type,1) = 'E' ;
 SELECT category, type FROM huge_table WHERE category = 'bar' AND LEFT(type,2) = 'EC' ;

如果key_len在所有这些情况下都相同(即category列的长度),则表明MySQL不会使用索引来检查{{ 1}}条件。

你会是对的。 MySQL在检查条件之前正在访问底层数据页面。

但是如果在某些情况下LEFT(type,2) =更长,那表明MySQL在查找行之前正在从索引中检查条件。

您可能还会在SELECT列表中没有包含key_len列的查询中获取EXPLAIN。