为什么 MySQL 不使用具有更高基数的索引?

Ray*_*der 7 mysql myisam mysql-5 index

我有一个source基数为 1122 的索引和一个state基数为 22 的state索引。MySQL在查询时一直使用该索引SELECT C1 FROM tbl WHERE source = 'x' and state = 'y'。这是正常的吗?如果没有,可能会出现什么问题?

Rol*_*DBA 12

基数

即使基数很高,MySQL 查询优化器使用的临界点还是密钥分发或存储引擎。

回到 2012 年 11 月 13 日,我讨论了不平衡的键如何使查询优化器选择不同的索引(有时根本不选择和索引):索引必须覆盖所有选定的列才能用于 ORDER BY?

在那篇文章中,我加载了一个表调用mf(男性女性)并存储了性别MF. 我插入了 37 Ms 和 3F。然后我运行了解释与 MyISAM 和 InnoDB 交叉的男性和女性值的计划。这些键和存储引擎的选择产生了一些有趣的结果。

您的查询

您需要做的是检查您的密钥的分布(或不平衡)情况。

您需要运行以下两个查询

SELECT IFNULL(source,'Total'),COUNT(1) RowCount
FROM tbl GROUP BY source WITH ROLLUP;

SELECT IFNULL(state,'Total'),COUNT(1) RowCount
FROM tbl GROUP BY state WITH ROLLUP;
Run Code Online (Sandbox Code Playgroud)

这将为您提供每个值的计数以及总行数。

在我之前的帖子中,我说过我使用 5% 的经验法则。查看每个值的行数。如果任何特定源或状态的行数超过表行数的 5%,查询优化器将在总线下抛出一个索引并尝试另一个索引。在极少数情况下,它可能只是做一次全表扫描。

如果任何具有低行数的键不能产生良好的索引选择,您可能需要重新计算 MyISAM 表的索引统计信息(特别是如果 MyISAM 表经历了大量 INSERT、UPDATE 和 DELETE,从而使索引统计信息过时) . 只需运行这个:

ANALYZE TABLE tbl;
Run Code Online (Sandbox Code Playgroud)

或者要对 MyISAM 表进行碎片整理并重新计算索引统计信息,请运行

OPTIMIZE TABLE tbl;
Run Code Online (Sandbox Code Playgroud)

试一试 !!!

更新 2014-04-12 18:06 EDT

我喜欢 Alexandros (+1 for you) 发布的答案

我想通过他的回答更新和扩展我的解释。

您提到了以下基数

  • state 有 22
  • source 有 1122

既然如此,就需要运行以下命令

ALTER TABLE tbl DROP INDEX state;
ALTER TABLE tbl ADD INDEX state_source_index (state,source);
Run Code Online (Sandbox Code Playgroud)

不要颠倒列的顺序。您应该始终首先索引较低基数的列。

好的,创建该索引将使您的查询变得更好。或者,会吗???

创建索引后,请运行此查询

SELECT
    IF(ISNULL(state)=1,'Total',
    CONCAT('Total Sources for ',state)) Statistic,RowCount
FROM
(
    SELECT state,source COUNT(1) RowCount
    FROM tbl GROUP BY state,source WITH ROLLUP
) A;
Run Code Online (Sandbox Code Playgroud)

这将为您提供(state,source)表中所有组合键的计数以及每个州的小计。任何RowCount超过该表的5%的任意组合键将最有可能导致表扫描。

更新 2014-04-15 16:43 EDT

你的最后一条评论是

我从你的回答中学到了很多。谢谢你。当键和多列键不平衡时,您会建议什么策略?我不明白为什么我应该先索引较低的基数列?

根据您需要的查询类型,列的顺序将有所帮助。

如果你运行这样的查询

  • SELECT ... FROM tbl ORDER BY state,source;
  • SELECT ... FROM tbl WHERE state='NY' ORDER BY source;
  • SELECT source,COUNT(1) RowCount FROM tbl WHERE state='NY' GROUP BY source;

那么,索引应该是 (state,source)

如果你运行这样的查询

  • SELECT ... FROM tbl ORDER BY source,state;
  • SELECT ... FROM tbl WHERE source='blog' ORDER BY state;
  • SELECT state,COUNT(1) RowCount FROM tbl WHERE source='blog' GROUP BY state;

那么,索引应该是(source,state)

我通常首先使用具有较低基数的列行,以使索引扫描更顺畅,以便在 ORDER BY 或 GROUP BY 以及其他列中涉及较低基数的查询。

如果您对索引(state,source)不满意,您可以同时制作

ALTER TABLE tbl ADD INDEX state_source_index (state,source);
ALTER TABLE tbl ADD INDEX source_state_index (source,state);
Run Code Online (Sandbox Code Playgroud)

对您的查询运行 EXPLAIN 计划,看看哪个索引被更频繁地使用。

无论键组合如何不平衡,无论列在复合索引中的顺序如何,如果使用的列按组编入索引,则评估数据的范围和顺序会更快。

更新 2014-04-16 12:17 EDT

如果每个(source,state)组合键都需要按 排序date,则需要将该date列合并到索引中作为最后一列。

ALTER TABLE tbl ADD INDEX state_source_date_index (state,source,date);
ALTER TABLE tbl ADD INDEX source_state_date_index (source,state,date);
Run Code Online (Sandbox Code Playgroud)

这样就ORDER BY不必触发排序。