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

为什么使用CHAR/VARCHAR索引时MySQL查询的性能如此糟糕?,mysql,sql,performance,optimization,indexing,Mysql,Sql,Performance,Optimization,Indexing,首先,我将描述问题域的简化版本 有两个表字符串: 如您所见,它有一个非唯一索引CHAR3 column 使用以下脚本填充该表: 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'),('À'),('Á'),('Â'),

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

有两个表字符串:

如您所见,它有一个非唯一索引CHAR3 column

使用以下脚本填充该表:

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个无法区分的字符串AAA的utf8_unicode_ci排序规则变体和字符串BBB的一个变体:

SELECT value, COUNT(*) FROM strings GROUP BY value;
+----+-----+ |值|计数*| +----+-----+ |AAA | 216000| |BBB | 1| +----+-----+ 随着值被索引,我希望以下两个查询具有相似的性能:

SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'AAA';
SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'BBB';
但在实践中,第一个比第二个慢300倍多!见:

+-----+------+--------------------------------+ |查询| ID |持续时间|查询| +-----+------+--------------------------------+ |1 | 0.11749275 |从值为'AAA'的字符串中选择SQL_NO_缓存计数*| |2 | 0.00033325 |从值为'BBB'的字符串中选择SQL_NO_缓存计数*| |3 | 0.11718050 |从值为'AAA'的字符串中选择SQL_NO_缓存计数*| +-----+------+--------------------------------+ -我在这里运行了两次“AAA”查询只是为了确定

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

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


我强烈感觉MySQL对索引键匹配的所有值执行完全线性扫描。但是,当它只返回匹配行的计数时,为什么要这样做呢?我是否错过了一些真正需要的东西?或者这是MySQL优化器的一个令人伤心的缺点?

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

这个答案的其余部分是猜测

性能只慢300倍而不是20万倍的原因是读取索引的开销。实际上,与其他需要的操作相比,扫描条目的速度相当快

在进行比较时,数字和字符串之间有着根本的区别。引擎只需查看两个数字的位表示,就可以识别它们是相同的还是不同的。不幸的是,对于字符串,您需要考虑编码/排序规则。我认为这就是为什么它需要关注价值观的原因


如果您有216000个完全相同的字符串副本,那么MySQL可能能够使用索引中的元数据进行计数。换句话说,索引器足够聪明,可以使用元数据进行精确的相等性比较。但是,考虑编码还不够聪明。

您可能需要检查的一件事是每个查询的逻辑I/O。我相信你会看到很大的不同。要计算表中“BBB”的数量,可能只需要3或4个LIO,具体取决于桶大小等因素。要计算“AAA”的数量,基本上必须扫描整个表,不管是否索引。对于216k行,这可以增加显著更多的LIO——更不用说物理I/O了。逻辑I/O比物理I/O快,但任何I/O都是性能杀手


至于文本与数字的比较,任何软件都比数据库引擎更容易、更快地比较数字。

@dnoeth。OP提出了一项声明,我希望他/她能够证实整型列的性能非常相似。问题是为什么索引扫描字符,而其他方法扫描整数;选择显示会话状态,如“处理程序%”;将为您提供执行选择所需的相对努力的良好感觉。您可能会发现“AAA”的数字约为216K,“BBB”的数字约为1。因为OP运行了两次,而且由于内存的原因,没有实际的IO。我从来没有听说MySQL足够聪明,可以从元数据计算出这样的IO。可能有太多的最终案例,不值得添加这样的优化。统计数据确实使用了这种方法——请参见解释中的行;并注意它有多频繁地出错。
SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'AAA';
SELECT SQL_NO_CACHE COUNT(*) FROM strings WHERE value = 'BBB';