使用CHAR / VARCHAR索引时为什么MySQL查询的性能如此糟糕?

时间:2015-05-25 13:51:27

标签: mysql sql performance optimization indexing

首先,我将描述问题域的简化版本。

有表strings

CREATE TABLE strings (
  value CHAR(3) COLLATE utf8_unicode_ci NOT NULL,
  INDEX(value)
) ENGINE=InnoDB;

如您所见,它具有CHAR(3)列的非唯一索引。

使用以下脚本填充表格:

CREATE TABLE a_variants (
  letter CHAR(1) COLLATE utf8_unicode_ci  NOT NULL
) ENGINE=MEMORY;

INSERT INTO a_variants VALUES -- 60 variants of letter 'A'
  ('A'),('a'),('À'),('Á'),('Â'),('Ã'),('Ä'),('Å'),('à'),('á'),('â'),('ã'),
  ('ä'),('å'),('Ā'),('ā'),('Ă'),('ă'),('Ą'),('ą'),('Ǎ'),('ǎ'),('Ǟ'),('ǟ'),
  ('Ǡ'),('ǡ'),('Ǻ'),('ǻ'),('Ȁ'),('ȁ'),('Ȃ'),('ȃ'),('Ȧ'),('ȧ'),('Ḁ'),('ḁ'),
  ('Ạ'),('ạ'),('Ả'),('ả'),('Ấ'),('ấ'),('Ầ'),('ầ'),('Ẩ'),('ẩ'),('Ẫ'),('ẫ'),
  ('Ậ'),('ậ'),('Ắ'),('ắ'),('Ằ'),('ằ'),('Ẳ'),('ẳ'),('Ẵ'),('ẵ'),('Ặ'),('ặ');

INSERT INTO strings
  SELECT CONCAT(a.letter, b.letter, c.letter) -- 60^3 variants of string 'AAA'
    FROM a_variants a, a_variants b, a_variants c
  UNION ALL SELECT 'BBB'; -- one variant of string 'BBB'

因此,它包含216000个不可区分的(就utf8_unicode_ci校对而言)字符串变体" AAA"字符串" BBB"的一个变体:

SELECT value, COUNT(*) FROM strings GROUP BY value;
+-------+----------+
| value | COUNT(*) |
+-------+----------+
| AAA   |   216000 |
| BBB   |        1 |
+-------+----------+

value被编入索引时,我希望以下两个查询具有相似的性能:

SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'AAA';
SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'BBB';

但在实践中,第一个比第二个慢<<> 300倍!参见:

+----------+------------+---------------------------------------------------------------+
| Query_ID | Duration   | Query                                                         |
+----------+------------+---------------------------------------------------------------+
|        1 | 0.11749275 | SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'AAA' |
|        2 | 0.00033325 | SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'BBB' |
|        3 | 0.11718050 | SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'AAA' |
+----------+------------+---------------------------------------------------------------+

- 我跑了AAA&#39;在这里查询两次只是为了确定。

如果我更改索引列的大小或将其类型更改为VARCHAR,性能问题仍然会出现。同时,在类似情况下,但当非唯一索引不是CHAR / VARCHAR(例如INT)时,查询速度与预期一样快。

所以,问题是为什么在使用CHAR / VARCHAR索引时MySQL查询的性能如此糟糕?

我强烈感觉MySQL对索引键匹配的所有值执行全线性扫描。但是为什么它只能返回匹配行的计数呢?我错过了什么,这真的需要吗?或者这是MySQL优化器的一个可悲的缺点?

2 个答案:

答案 0 :(得分:1)

显然,问题是查询正在进行索引扫描。另一种方法是对第一个和最后一个相同的值进行两次索引查找,然后在索引中使用元信息进行计算。根据您的观察,MySQL可以做到这两点。

这个答案的其余部分是推测。

性能“仅”慢300倍而不是200,000倍的原因是因为读取索引的开销。实际上,与其他需要的操作相比,扫描条目的速度非常快。

在比较时,数字和字符串之间存在根本区别。引擎只需查看两个数字的位表示,并识别它们是相同还是不同。不幸的是,对于字符串,您需要考虑编码/整理。我认为这就是它需要关注价值的原因。

如果您有<216>份完全相同的字符串,那么MySQL可能会使用索引中的元数据进行计数。换句话说,索引器足够智能,可以使用元数据进行精确的相等比较。但是,将编码考虑在内并不够聪明。

答案 1 :(得分:0)

您可能需要检查的一件事是每个查询的逻辑I / O.我相信你会发现很大的不同。要计算表中BBB的数量,可能只需要3或4个LIO(取决于桶大小等)。要计算“AAA”的数量,基本上必须扫描整个表,索引与否。拥有216k行,可以增加更多的LIO - 更不用说物理I / O.逻辑I / O比物理I / O更快,但任何I / O都是性能杀手。

对于文本与数字,软件(任何软件,而不仅仅是数据库引擎)比数字更容易和更快地比较数字。