To kolejne podejście.
To zapytanie ma takie same problemy z wydajnością, jak inne zapytania, które zwracają poprawne wyniki, ponieważ plan wykonania dla tego zapytania będzie wymagał operacji SORT na KAŻDYM wierszu w tabeli statystyk. Ponieważ nie ma predykatu (ograniczenia) w kolumnie czasu, będzie brany pod uwagę KAŻDY wiersz w tabeli statystyk. Dla NAPRAWDĘ dużego stołu stats
, to spowoduje zdmuchnięcie całej dostępnej tymczasowej przestrzeni, zanim umrze straszliwa śmierć. (więcej Uwagi dotyczące wydajności poniżej).
SELECT r.*
, IFNULL(s.avg_votes,0)
FROM servers r
LEFT
JOIN (SELECT t.server
, AVG(t.votes) AS avg_votes
FROM (SELECT CASE WHEN u.server = @last_server
THEN @i := @i + 1
ELSE @i := 1
END AS i
, @last_server := u.server AS `server`
, u.votes AS votes
FROM (SELECT @i := 0, @last_server := NULL) i
JOIN (SELECT v.server, v.votes
FROM stats v
ORDER BY v.server DESC, v.time DESC
) u
) t
WHERE t.i <= 24
GROUP BY t.server
) s
ON s.server = r.id
Co robi ta kwerenda jest sortowanie tabeli statystyki przez serwer i malejącej na kolumnie czasu. (Widok śródliniowany jako u
.)
W posortowanym zestawie wyników przyporządkowujemy numery rzędów 1,2,3 itd. Do każdego rzędu dla każdego serwera. (Widok śródliniowy z aliasami jako t
.)
Z tym zestawem wyników odfiltrowujemy wszystkie wiersze z rownumber> 24 i obliczamy średnią z kolumny votes
dla "najnowszych" 24 wierszy dla każdego serwera. (Widok śródliniowany jako s
.)
Ostatnim krokiem jest połączenie tego z tabelą serwerów, aby zwrócić żądany zestaw wyników.
UWAGA:
plan wykonania dla tego zapytania będzie kosztowne dla dużej liczby wierszy w tabeli stats
.
Aby poprawić wydajność, możemy przyjąć kilka podejść.
Najprostszym moc należy uwzględnić w zapytaniu do orzeczenie Wyklucza znaczna liczba rzędów z tabeli stats
(np rzędy z time
wartości około 2 dni, lub ponad 2 tygodni). To znacznie zmniejszyłoby liczbę wierszy, które muszą być posortowane, aby określić "najnowsze" 24 wiersze.
Ponadto, z indeksem na stats(server,time)
, możliwe jest również, że MySQL może wykonać względnie efektywne "skanowanie wsteczne" na indeksie, unikając operacji sortowania.
Możemy również rozważyć wdrożenie indeksu w tabeli statystyk na (server,"reverse_time")
. Ponieważ MySQL nie obsługuje jeszcze indeksów malejących, implementacja byłaby naprawdę regularnym (rosnącym) indeksem dla pochodnej wartości rtime
(wyrażenie "odwróconego czasu", które jest rosnące dla malejących wartości time
(na przykład -1*UNIX_TIMESTAMP(my_timestamp)
lub -1*TIMESTAMPDIFF('1970-01-01',my_datetime)
.
Innym sposobem na zwiększenie wydajności byłoby zachowanie tabeli cienia zawierającej ostatnie 24 wiersze dla każdego serwera. Byłoby to najprostsze rozwiązanie, gdybyśmy mogli zagwarantować, że "najnowsze wiersze" nie zostaną usunięte z tabeli stats
Możemy utrzymać tę tabelę z wyzwalaczem.Zasadniczo, gdy wiersz jest wstawiany do tabeli stats
, sprawdzamy, czy time
w nowych wierszach jest późniejszy niż najwcześniejszy time
przechowywany dla serwera w cieniu Jeśli tak jest, zastępujemy najwcześniejszy wiersz w tabeli cieni nowym wierszem, pamiętając o tym, aby w tabeli cienia przechowywać nie więcej niż 24 wiersze dla każdego serwera.
I jeszcze innym podejściem jest napisanie procedury lub funkcji, która daje wynik. Podejściem tutaj byłoby przechodzenie przez każdy serwer i uruchamianie oddzielnego zapytania względem tabeli statystyk, aby uzyskać średnią votes
dla ostatnich 24 wierszy i zebranie wszystkich tych wyników razem. (Takie podejście może być raczej obejściem, aby uniknąć pewnego rodzaju ogromnego zestawu tymczasowego, aby umożliwić powrót zestawu wyników, niekoniecznie powodując niesamowicie szybki powrót zestawu wyników.)
Najważniejsze dla wydajności ten typ zapytania w tabeli LARGE ogranicza liczbę wierszy rozpatrywanych przez zapytanie I unika operacji sortowania na dużym zestawie. Tak otrzymujemy kwerendę do wykonania.
DODATEK
Aby dostać „skanowanie reverse index” operacji (aby uzyskać wiersze z stats
zamówione przy użyciu indeksu BEZ operacji filesort), musiałem podać malejącej na obu wyrażeń w Klauzula ORDER BY. Poprzednio powyższe zapytanie miało ORDER BY server ASC, time DESC
, a MySQL zawsze chciał zrobić plik, nawet określając podpowiedź FORCE INDEX FOR ORDER BY (stats_ix1)
.
Jeśli wymagane jest zwrócenie "średniej liczby głosów" dla serwera tylko, jeśli w tabeli statystyk znajduje się co najmniej 24 powiązane wiersze, możemy wykonać bardziej efektywne zapytanie, nawet jeśli jest ono nieco bardziej niechlujny. (Większość bałaganu w zagnieżdżonych funkcjach IF() polega na radzeniu sobie z wartościami NULL, które nie są uwzględniane w średniej, może być znacznie mniej niechlujny, jeśli mamy gwarancję, że votes
NIE jest NULL, lub jeśli wykluczymy jakiekolwiek wiersze gdzie votes
jest puste.)
SELECT r.*
, IFNULL(s.avg_votes,0)
FROM servers r
LEFT
JOIN (SELECT t.server
, t.tot/NULLIF(t.cnt,0) AS avg_votes
FROM (SELECT IF(v.server = @last_server, @num := @num + 1, @num := 1) AS num
, @cnt := IF(v.server = @last_server,IF(@num <= 24, @cnt := @cnt + IF(v.votes IS NULL,0,1),@cnt := 0),@cnt := IF(v.votes IS NULL,0,1)) AS cnt
, @tot := IF(v.server = @last_server,IF(@num <= 24, @tot := @tot + IFNULL(v.votes,0) ,@tot := 0),@tot := IFNULL(v.votes,0) ) AS tot
, @last_server := v.server AS SERVER
-- , v.time
-- , v.votes
-- , @tot/NULLIF(@cnt,0) AS avg_sofar
FROM (SELECT @last_server := NULL, @num:= 0, @cnt := 0, @tot := 0) u
JOIN stats v FORCE INDEX FOR ORDER BY (stats_ix1)
ORDER BY v.server DESC, v.time DESC
) t
WHERE t.num = 24
) s
ON s.server = r.id
o wskaźniku powłoką na stats(server,time,votes)
, wyjaśniania wykazały MySQL uniknąć operacji filesort, więc musi on być używany „skanowanie” odwrotnej indeksów wierszy powrót w porządku. Bez indeksu obejmującego i indeksu na "(serwer, czas) , MySQL used the index if I included an index hint, with the
SIŁA INDEKS NA ZAMÓWIENIE (stats_ix1)" podpowiedź, MySQL również unikał plików. (Ale ponieważ mój stół miał mniej niż 100 wierszy, nie sądzę, aby MySQL kładł duży nacisk na unikanie operacji na plikach.)
Wyrażenia czasu, głosów i wyrazów avg_sofar są komentowane (w widoku liniowym pod aliasem t
); nie są potrzebne, ale służą do debugowania.
Sposób, w jaki zapytanie się znajduje, wymaga co najmniej 24 wierszy w statystykach dla każdego serwera, aby zwrócić średnią. (Może to być dopuszczalne.) Ale myślałem, że ogólnie rzecz biorąc, możemy zwrócić sumę bieżącą, sumę do tej pory (tot) i liczbę uruchomień (cnt).
(Gdybyśmy wymienić WHERE t.num = 24
z WHERE t.num <= 24
, widzimy uruchomiony średnio w akcji.)
Aby powrócić średnią tam, gdzie nie są na co najmniej 24 wierszy w statystykach, to naprawdę kwestia identyfikacji wiersz (dla każdego serwera) z maksymalną wartością liczby równą < = 24.
Wierzę, że to (http://sqlfiddle.com/#!2/d908f/5) jest strukturą tabeli w tabeli . Dobrze? –