2015-05-25 54 views
1

首先,我將描述問題域的簡化版本。爲什麼使用CHAR/VARCHAR索引時,MySQL查詢的性能如此糟糕?

有表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'查詢兩次,這裏只是爲了確定。

如果我更改索引列的大小或更改其類型設置爲VARCHAR,具有性能問題依然表現。同時,在類似的情況下,但是當非唯一索引不是CHAR/VARCHAR(例如INT)時,查詢與預期的一樣快。

所以,問題是爲什麼使用CHAR/VARCHAR索引時,MySQL查詢的性能如此糟糕?

我有強烈的感覺是,MySQL執行按索引鍵相匹配的所有值完全線性掃描。但是,爲什麼它只能返回匹配行的數量呢?我錯過了什麼,這真的需要嗎?或者是MySQL優化器的一個可悲的缺點?

+1

@dnoeth。 。 。 OP做出聲明(我希望他/他會證實)整數列的性能非常相似。問題是爲什麼一個索引掃描字符,但其他一些整數方法。 –

回答

1

顯然,問題是,查詢做一個索引掃描。另一種方法是做兩個索引查找,第一個和最後一個相同的值,然後在索引中使用元信息進行計算。根據你的觀察,MySQL可以兼得。

這個答案的其餘部分是投機。

究其原因,性能會降低「只有」 300倍,速度較慢,而不是20萬次,是因爲開銷在讀取索引。實際上,與其他需要的操作相比,掃描條目相當快。

有數字和字符串之間的根本區別,當談到比較。引擎可以只看兩個數字的位表示,並識別它們是相同的還是不同的。不幸的是,對於字符串,您需要考慮編碼/整理。我認爲這就是爲什麼它需要看待價值。

如果您有216,000個正好相同的字符串,那麼MySQL可以使用索引中的元數據進行計數。換句話說,索引器足夠聰明,可以使用元數據進行精確的相等比較。但是,考慮到編碼並不夠智能。

+0

我從來沒有聽說MySQL足夠聰明,可以從「元數據」中進行計算。有可能有太多的終端情況使得增加這樣的優化值得值得。 「統計」確實使用了這些 - 參見'EXPLAIN'中的「行」;並注意它多久出錯。 –

0

您可能要檢查的一件事是每個查詢的邏輯I/O。我相信你會看到很大的不同。要計算表中'BBB的數量,可能只需要3或4個LIO(取決於存儲桶大小等)。要統計'AAA'的數量,必須掃描整個表格,索引與否。有了216k行,可以增加更多的LIO - 更不用說物理I/O了。邏輯I/O比物理I/O更快,但任何I/O都是性能殺手。

至於文本和數字,軟件(任何軟件,而不僅僅是數據庫引擎)比文本比較總是更容易和更快。

+0

'FLUSH STATUS;選擇 ...; SHOW SESSION STATUS LIKE'Handler%';'會給你一個很好的感覺,以執行SELECT所需的相對「努力」。您可能會發現「AAA」約爲216K,「BBB」約爲1。由於OP運行了兩次,並且因爲「MEMORY」,所以沒有_actual_ IO。 –

相關問題