首先,我将描述问题域的简化版本。
有表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优化器的一个可悲的缺点?
答案 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都是性能杀手。
对于文本与数字,软件(任何软件,而不仅仅是数据库引擎)比数字更容易和更快地比较数字。