MySQL:低基数/选择性列=如何索引?

时间:2010-03-05 13:01:22

标签: mysql database

我需要在我的表(列)中添加索引,并偶然发现了这篇文章:

How many database indexes is too many?

引用: “话虽如此,你可以清楚地向一个不会做任何事情的表中添加许多无意义的索引。将B-Tree索引添加到具有2个不同值的列将是没有意义的,因为它不会在查找数据方面添加任何内容。列中的值越独特,它就越能从索引中受益。“

如果只有两个不同的值,那么指数真的没有意义吗?给出如下表(MySQL数据库,InnoDB)

Id (BIGINT)
fullname (VARCHAR)
address (VARCHAR)
status (VARCHAR)

其他条件:

  • 数据库包含3亿条记录
  • 状态只能“启用”和“禁用”
  • 1.5亿条记录已启用状态=已启用1.5亿条记录 stauts = disabled

我的理解是,如果没有状态索引,使用where status=’enabled’的选择会导致完整的表扫描有3亿条记录要处理?

当我在状态上使用BTREE索引时,查找效率如何?

我应该索引此列吗?

MySQL InnoDB提供了哪些替代方案(可能是任何其他索引),通过给定示例中的“where status =”enabled“子句有效地查看记录,其中基数/选择性的值非常低?

7 个答案:

答案 0 :(得分:37)

您描述的索引几乎毫无意义。当您需要选择行数与总行数进行比较时,最好使用索引。

其原因与数据库访问表的方式有关。可以通过全表扫描来评估表,其中每个块依次被读取和处理。或者通过rowid或键查找,其中数据库具有键/ rowid并读取它所需的确切行。

如果您使用基于主键或其他唯一索引的where子句,例如。 where id = 1,数据库可以使用索引来准确引用行的数据存储位置。这显然比进行全表扫描和处理每个块更有效。

现在回到您的示例,您有一个where status = 'enabled'的where子句,索引将返回150m行,数据库必须使用单独的小读取依次读取每一行。而使用全表扫描访问表允许数据库使用更有效的更大读取。

有一点可以更好地进行全表扫描而不是使用索引。使用mysql,您可以使用FORCE INDEX (idx_name)作为查询的一部分,以便在每个表访问方法之间进行比较。

参考: http://dev.mysql.com/doc/refman/5.5/en/how-to-avoid-table-scan.html

答案 1 :(得分:11)

我很遗憾地说我不同意迈克。添加索引意味着限制MySQL的完整记录搜索量,从而限制IO,这通常是瓶颈。

此索引不是免费的;你需要更新索引时在插入/更新和搜索本身上付费,因为它现在需要加载索引文件(300M记录的全文索引可能不在内存中)。所以很可能你得到额外的 IO而不是限制它。

我同意这样的说法,即二进制变量最好存储为一个bool或tinyint,因为它会减少行的长度,从而限制磁盘IO,同时数字的比较也会更快。

如果您需要速度并且很少使用禁用的记录,您可能希望有2个表,一个用于启用,一个用于禁用记录,并在状态更改时移动记录。由于它增加了复杂性和风险,这将是我最后的选择。如果你碰巧去了它,一定要在1个交易中进行。

我只是突然想到你可以使用explain 语句检查索引是否实际使用。这应该向您展示MySQL如何优化查询。我真的不知道MySQL优化查询,但是从postgresql我知道你应该在数据库上解释一个与真实数据库大致相同(大小和数据)的查询。因此,如果您在数据库上有一个副本,请在表上创建一个索引,然后查看它实际使用的是什么。正如我所说,我对此表示怀疑,但我绝对不知道所有事情:)

答案 2 :(得分:6)

如果数据的分发时间为50:50,那么查询status="enabled"将避免半扫描表。

在这些表上拥有索引完全取决于数据的分布,即:如果启用状态的条目是90%而其他条目是10%。对于查询status="disabled",它只扫描表的10%。

所以在这些列上有索引取决于数据的分布。

答案 3 :(得分:4)

您几乎不需要同时获得所有150万条记录,因此我猜“状态”将始终与其他列一起使用。也许使用像(status,fullname)这样的复合索引更有意义

答案 4 :(得分:3)

Jan,你绝对应该索引该列。我不确定报价的背景,但你上面说的一切都是正确的。如果没有该列的索引,您肯定会对300M行进行表扫描,这是您可以对该数据执行的最差操作。

Jan,有问题,您的查询只涉及“where status = enabled”而没有其他限制因素,该列的索引显然无济于事(很高兴SO社区向我展示了什么)。但是,如果存在限制因素,例如“限制10”,则索引可能有所帮助。此外,请记住,索引也按分组使用,并按优化顺序排列。如果您正在执行“按状态选择计数(*),来自表组的状态”,则索引会很有帮助。

您还应该考虑将状态转换为tinyint,其中0表示禁用,1表示启用。你浪费了大量的空间存储该字符串而不是每行只需要1个字节的tinyint!

答案 5 :(得分:1)

@ a'r答案是正确的,但需要指出的是,索引的有用性不仅取决于其基数,还取决于数据的分布和数据库上运行的查询。

在OP的情况下,150M记录的status='enabled'和150M的记录status='disabled',索引是不必要的,浪费资源。

如果299M记录的status='enabled'和1M的status='disabled',则索引在SELECT ... where status='disabled'类型的查询中很有用(并将被使用)。
类型SELECT ... where status='enabled'的查询仍将使用全表扫描运行。

答案 6 :(得分:0)

我的MySQL数据库中有一个类似的专栏。大约400万行,分布分别为90%1和10%0。

我今天刚刚发现,我的查询(where column = 1)实际上在没有索引的情况下运行得明显更快。

我愚蠢地删除了索引。我说的很愚蠢,因为我现在怀疑查询(where column = 0)可能仍然从中受益。因此,相反,我应该明确地告诉MySQL在搜索1时忽略索引,而在搜索0时使用索引。也许。