Oto, co się dzieje.
The SELECT COUNT (...) icd_index where icd='25000'
użyje indeksu, który jest BTree oddzielonym od danych. Ale skanuje to w ten sposób:
- Znajdź pierwszy wpis mający icd='25000'. To jest prawie natychmiastowe.
- Skanuj do przodu, dopóki nie znajdzie zmiany w icd. Spowoduje to skanowanie tylko w indeksie, bez dotykania danych. Według EXPLAIN będzie około 910 104 wpisów indeksu do przejrzenia.
Teraz spójrzmy na BTree dla tego indeksu. W oparciu o pola w indeksie każdy wiersz będzie miał dokładnie 22 bajty, plus będzie trochę narzutu (szacunkowo 40%). Blok indeksu MyISAM ma 1 KB (por. 16 KB w InnoDB). Oszacowałbym 33 rzędy na blok. 910,104/33 mówi, że aby wykonać ZLICZANIE, należy odczytać około 27K bloków. (Uwaga COUNT(core_id)
musi sprawdzić core_id
za bycie pustym, COUNT(*)
nie; jest to niewielka różnica.) Odczytanie 27K bloków na zwykłym dysku twardym zajmuje około 270 sekund. Miałeś szczęście, że udało Ci się zrobić to w 60 sekund.
Drugie uruchomienie znalazło wszystkie te bloki w key_buffer (zakładając, że key_buffer_size ma co najmniej 27MB), więc nie musiał czekać na dysk. Stąd było znacznie szybciej. (To ignorowanie pamięci podręcznej zapytań, którą miałeś mądrość, aby opróżnić lub użyć SQL_NO_CACHE.)
5.6 jest nieistotny (ale dzięki za wspomnienie o tym), ponieważ ten proces nie zmienił się od 4.0 lub wcześniej (z wyjątkiem tego, że utf8 nie istniał; więcej o tym poniżej).
Przejście na InnoDB pomogłoby na kilka sposobów. KLUCZ PODSTAWOWY byłby „zgrupowany” z danymi, a nie przechowywany jako oddzielny BTree. W związku z tym, gdy dane lub PK zostaną zapisane w pamięci podręcznej, druga jest natychmiast dostępna. Liczba bloków byłaby bardziej zbliżona do 5K, ale byłyby to bloki 16KB. Mogą one być ładowane szybciej, jeśli pamięć podręczna jest zimna.
Pytasz "Czy potrzebuję indeksu tylko dla icd?" -- Cóż, to zmniejszyłoby rozmiar MyISAM BTree do około 21 bajtów na wiersz, więc BTree byłoby około 21/27 wielkości, niewiele się poprawi (przynajmniej dla sytuacja zimnej pamięci podręcznej).
Inna myśl brzmi:jeśli icd
jest zawsze numeryczny i zawsze numeryczny, aby użyć MEDIUMINT UNSIGNED
i dodaj ZEROFILL
jeśli może mieć wiodące zera.
Ups, nie zauważyłem ZESTAWU ZNAKÓW. (Poprawiłem powyższe liczby, ale pozwól, że rozwinę.)
- CHAR(5) pozwala na 5 znaków .
- Ascii zajmuje 1 bajt na znak .
- utf8 zajmuje do 3 bajtów za znaki .
- Więc CHAR(5) ZESTAW ZNAKÓW utf8 zajmuje 15 bajtów zawsze .
Zmiana kolumny na CHAR(5) CHARACTER SET ascii
zmniejszy go do 5 bajtów.
Zmiana na MEDIUMINT UNSIGNED ZEROFILL zmniejszyłaby go do 3 bajtów.
Zmniejszenie danych przyspieszyłoby operacje we/wy o mniej więcej proporcjonalną wartość (po pozostawieniu kolejnych 6 bajtów na pozostałe dwa pola).