我拉着我的头发试图找出我做错了什么。 表非常简单:
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行获得的最佳性能吗?为什么运行查询然后重置缓存会影响速度?结果是否存储在索引中?
以下是查询的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
答案 0 :(得分:0)
这是发生了什么。
The SELECT COUNT (...) icd_index where icd='25000'
将使用索引,它是与数据分开的BTree。但它以这种方式扫描它:
现在让我们看看该指数的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) CHARACTER SET ascii
会将其缩小为5个字节。
将其更改为MEDIUMINT UNSIGNED ZEROFILL会将其缩小为3个字节。
缩小数据会使I / O加速一个大致成比例的量(在另外两个字段允许另外6个字节之后。
答案 1 :(得分:0)
感谢以上所有人的帮助。鉴于上述建议,我完全重建了数据库:
上帝的圣母,现在真的很快。上面的简单计数查询现在运行不到2秒。不确定上述哪一项最有效(但在缓冲池大小增加之前查询速度很快)
答案 2 :(得分:0)
我的一个查询发生了同样的事情。 MyISAM表使用filesort来执行简单的SELECT语句。
我最终切换到InnoDB,问题就消失了。我不知道为什么。