尽管覆盖索引,MySQL MyISAM缓慢count()查询

时间:2015-03-07 22:48:39

标签: mysql sql

我拉着我的头发试图找出我做错了什么。 表非常简单:

CREATE TABLE `icd_index` (
  `icd` char(5) NOT NULL,
  `core_id` int(11) NOT NULL,
  `dx_order` tinyint(4) NOT NULL,
  PRIMARY KEY (`icd`,`dx_order`,`core_id`),
  KEY `core` (`core_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

正如您所看到的,我创建了一个涵盖表的所有三列的覆盖索引,以及core_id上的潜在联接的附加索引。这是一对多链接表,每个core_id映射到一个或多个icd。该表包含6500万行。

所以,这就是问题所在。假设我想知道有多少人拥有“25000”的icd代码。 [那是糖尿病,万一你想知道]。我写了一个看起来像这样的查询:

SELECT COUNT(core_id) FROM icd_index WHERE icd='25000'

这需要60秒才能执行。我曾经想过,因为icd列是覆盖索引中的第一列,所以计算它会很快。

更令人困惑的是,一旦我运行了一次查询,它现在运行得非常快。我认为那是因为查询被缓存了,但即使我RESET QUERY CACHE,查询现在也会在几分之一秒内运行。但是,如果我等待的时间足够长,它似乎再次放缓 - 我无法弄清楚原因。

我遗漏了一些明显的东西。我是否仅需要icd的索引?这是我用65M行获得的最佳性能吗?为什么运行查询然后重置缓存会影响速度?结果是否存储在索引中?

编辑:我正在运行MySQL 5.6(如果重要的话)。

以下是查询的EXPLAIN

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  icd_index   ref PRIMARY PRIMARY 15  const   910104  Using where; Using index

3 个答案:

答案 0 :(得分:0)

这是发生了什么。

The SELECT COUNT (...) icd_index where icd='25000'

将使用索引,它是与数据分开的BTree。但它以这种方式扫描它:

  1. 找到第一个有icd ='25000'的条目。这几乎是瞬间的。
  2. 向前扫描,直到找到icd中的更改。这将仅扫描索引,而不是触摸数据。根据EXPLAIN,将有大约910,104个索引条目进行扫描。
  3. 现在让我们看看该指数的BTree。根据索引中的字段,每行将精确地为22个字节,此外还会有一些开销(估计为40%)。 MyISAM索引块为1KB(参见InnoDB的16KB)。我估计每个块有33行。 910,104 / 33表示需要读取大约27K块来执行COUNT。 (注意COUNT(core_id)需要检查core_id是否为空,COUNT(*)不是;这是一个小的区别。)读取普通硬盘驱动器上的27K块需要大约270秒。你很幸运能在60秒内完成它。

    第二次运行找到key_buffer中的所有块(假设key_buffer_size至少为27MB),因此它不必等待磁盘。因此它要快得多。 (这忽略了查询缓存,你有智慧刷新或使用SQL_NO_CACHE。)

    5.6恰好是无关紧要的(但感谢提及),因为此过程自4.0或之前没有改变(除了utf8不存在;更多内容如下)。

    切换到InnoDB会有两个方面的帮助。 PRIMARY KEY将与数据“聚集”,而不是作为单独的BTree存储。因此,一旦缓存了数据或PK,另一个就立即可用。块的数量将更像是5K,但它们将是16KB块。如果缓存很冷,这些可以加载得更快。

    你问“我是否需要单独使用icd上的索引?” - 这样可以将MyISAM BTree的大小缩小到每行约21个字节,因此BTree的大小约为21/27,并没有太大的改进(在至少对于冷缓存情况而言。)

    另一个想法是,如果 icd始终是数字且始终是数字,则使用MEDIUMINT UNSIGNED,如果它可以有前导零,则ZEROFILL

    哎呀,我没注意到CHARACTER SET。 (我已经修正了上面的数字,但让我详细说明。)

    • CHAR(5)允许5 个字符
    • ascii每个字符需要1个字节
    • 每个字符最多需要3个字节
    • 因此,CHAR(5)字符集utf8需要15个字节 总是

    将列更改为CHAR(5) CHARACTER SET ascii会将其缩小为5个字节。

    将其更改为MEDIUMINT UNSIGNED ZEROFILL会将其缩小为3个字节。

    缩小数据会使I / O加速一个大致成比例的量(在另外两个字段允许另外6个字节之后。

答案 1 :(得分:0)

感谢以上所有人的帮助。鉴于上述建议,我完全重建了数据库:

  1. 我说服务器管理员将我的RAM增加到6G。
  2. 我用ASCII字符集将所有表格切换到InnoDB。
  3. 当我将数据从MyISAM移动到InnoDB时,我将所有数据按覆盖索引的顺序排序,然后将其插入到新表中,以便新表完全正确排序。不知道这是否真的有用,但似乎不会受到伤害。
  4. 我修改了数据库设置,特别是InnoDB缓冲池大小,并将其增加到256M。
  5. 上帝的圣母,现在真的很快。上面的简单计数查询现在运行不到2秒。不确定上述哪一项最有效(但在缓冲池大小增加之前查询速度很快)

答案 2 :(得分:0)

我的一个查询发生了同样的事情。 MyISAM表使用filesort来执行简单的SELECT语句。

我最终切换到InnoDB,问题就消失了。我不知道为什么。