Aby policzyć liczbę wierszy z konkretną datą, MySQL musi zlokalizować tę wartość w indeksie (co jest dość szybkie, przecież po to właśnie są tworzone indeksy), a następnie odczytywać kolejne wpisy indeksu dopóki nie znajdzie następnej daty. W zależności od typu danych esi
, będzie to oznaczać odczytanie kilku MB danych, aby policzyć 700k wierszy. Odczytanie niektórych MB nie zajmuje dużo czasu (a dane mogą być już zapisane w pamięci podręcznej w puli buforów, w zależności od tego, jak często używasz indeksu).
Aby obliczyć średnią dla kolumny, która nie jest uwzględniona w indeksie, MySQL ponownie użyje indeksu, aby znaleźć wszystkie wiersze dla tej daty (tak samo jak poprzednio). Ale dodatkowo, dla każdego znalezionego wiersza musi odczytać rzeczywiste dane tabeli dla tego wiersza, co oznacza użycie klucza podstawowego do zlokalizowania wiersza, odczytanie kilku bajtów i powtórzenie tego 700 tysięcy razy. Ten "dostęp losowy"
to dużo wolniejszy niż odczyt sekwencyjny w pierwszym przypadku. (To się pogarsza z powodu problemu, że "niektóre bajty" to innodb_page_size
(domyślnie 16KB), więc może być konieczne odczytanie do 700k * 16KB =11GB, w porównaniu do "kilka MB" dla count(*)
; i w zależności od konfiguracji pamięci, niektóre z tych danych mogą nie być buforowane i muszą być odczytane z dysku).
Rozwiązaniem jest uwzględnienie w indeksie wszystkich używanych kolumn („indeks pokrywający”), np. utwórz indeks na date, 01
. Wtedy MySQL nie musi mieć dostępu do samej tabeli i może kontynuować, podobnie jak pierwsza metoda, po prostu czytając indeks. Rozmiar indeksu nieco się zwiększy, więc MySQL będzie musiał odczytać "trochę więcej MB" (i wykonać avg
-operacja), ale nadal powinno to być kwestią sekund.
W komentarzach wspomniałeś, że musisz obliczyć średnią z 24 kolumn. Jeśli chcesz obliczyć avg
dla kilku kolumn jednocześnie potrzebny byłby indeks pokrycia wszystkich z nich, np. date, 01, 02, ..., 24
aby uniemożliwić dostęp do tabeli. Należy pamiętać, że indeks, który zawiera wszystkie kolumny, wymaga tyle miejsca do przechowywania, co sama tabela (a utworzenie takiego indeksu zajmie dużo czasu), więc może zależeć od tego, jak ważne jest to zapytanie, czy jest warte tych zasobów.
Aby uniknąć limitu MySQL 16 kolumn na indeks
, możesz podzielić go na dwa indeksy (i dwa zapytania). Utwórz m.in. indeksy date, 01, .., 12
i date, 13, .., 24
, a następnie użyj
select * from (select `date`, avg(`01`), ..., avg(`12`)
from mytable where `date` = ...) as part1
cross join (select avg(`13`), ..., avg(`24`)
from mytable where `date` = ...) as part2;
Upewnij się, że dobrze to udokumentowałeś, ponieważ nie ma oczywistego powodu, aby pisać zapytanie w ten sposób, ale może to być tego warte.
Jeśli uśredniasz tylko jedną kolumnę, możesz dodać 24 oddzielne indeksy (w date, 01
, date, 02
, ...), choć w sumie będą wymagały jeszcze więcej miejsca, ale mogą być nieco szybsze (ponieważ są mniejsze pojedynczo). Ale pula buforów może nadal faworyzować pełny indeks, w zależności od czynników, takich jak wzorce użytkowania i konfiguracja pamięci, więc może być konieczne przetestowanie tego.
Od date
jest częścią twojego klucza podstawowego, możesz również rozważyć zmianę klucza podstawowego na date, esi
. Jeśli znajdziesz daty według klucza podstawowego, nie będziesz potrzebować dodatkowego kroku, aby uzyskać dostęp do danych tabeli (ponieważ już uzyskujesz dostęp do tabeli), więc zachowanie będzie podobne do indeksu pokrywającego. Ale jest to znacząca zmiana w Twojej tabeli i może wpłynąć na wszystkie inne zapytania (które np. używają esi
aby zlokalizować wiersze), więc należy to dokładnie rozważyć.
Jak wspomniałeś, inną opcją byłoby zbudowanie tabeli podsumowującej, w której przechowujesz wstępnie obliczone wartości, zwłaszcza jeśli nie dodajesz ani nie modyfikujesz wierszy z przeszłych dat (lub możesz je aktualizować za pomocą wyzwalacza).