MySQL: Zoptymalizuj UNION z „ORDER BY” w zapytaniach wewnętrznych


9

Właśnie skonfigurowałem system rejestrowania, który składa się z wielu tabel o tym samym układzie.

Dla każdego źródła danych istnieje jedna tabela.

W przypadku przeglądarki dziennika chcę

  • UNION wszystkie tabele dziennika ,
  • filtruj je według konta ,
  • dodaj pseudokolumnę do identyfikacji źródła,
  • posortuj je według czasu ,
  • i ogranicz je do stronicowania .

Wszystkie tabele zawierają pole o nazwie zeitpunktindeksowana kolumna daty / godziny.

Moja pierwsza próba to:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730)

ORDER BY zeit DESC LIMIT 10;

Optymalizator nie może tutaj użyć indeksów, ponieważ wszystkie wiersze z obu tabel są zwracane przez podzapytania i sortowane po UNION.

Moje obejście było następujące:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

ORDER BY zeit DESC LIMIT 10;

Spodziewałem się, że silnik zapytań użyje tutaj indeksów, ponieważ oba podkwerendy powinny zostać posortowane i ograniczone już przed UNION, który następnie łączy i sortuje wiersze.

Naprawdę myślałem, że to będzie to, ale uruchomienie EXPLAINzapytania powoduje, że podkwerendy wciąż przeszukują obie tabele.

EXPLAINingsame podkwerendy pokazują mi pożądaną optymalizację, ale UNIONingrazem ich nie robi.

Przegapiłem coś?

Wiem, że ORDER BYklauzule w UNIONpodzapytaniach są ignorowane bez znaku LIMIT, ale jest pewien limit.

Edycja:
W rzeczywistości prawdopodobnie będą też zapytania bezaccount_idwarunku.

Tabele już istnieją i są wypełnione danymi. Mogą występować zmiany w układzie w zależności od źródła, dlatego chcę je podzielić. Ponadto klienci rejestrujący używają różnych poświadczeń z określonego powodu.

Muszę zachować rodzaj warstwy między czytnikami dzienników a rzeczywistymi tabelami.

Oto plany wykonania dla całego zapytania i pierwszego podzapytania, a także szczegółowo układ tabeli:

https://gist.github.com/ca8fc1093cd95b1c6fc0


1
Najlepszym wskaźnikiem do tego byłby związek (account_id, zeitpunkt). Czy masz taki indeks? Drugi najlepszy byłby (myślę) singiel (zeitpunkt)- ale wydajność, jeśli zostanie użyta, zależy od tego, jak często account_id=730pojawiają się wiersze .
ypercubeᵀᴹ

2
A dlaczego UNION DISTINCT? Nie ma potrzeby wymuszania sortowania i rozróżniania tam, ponieważ wyniki będą różne dla podkwerend, ze względu na dodatkową kolumnę identyfikacyjną. Zastosowanie UNION ALL.
ypercubeᵀᴹ

1
Oprócz sugestii @ ypercube mam pytanie: czy nie byłoby lepiej mieć wszystkie te dzienniki w tej samej tabeli z dodaną sourcekolumną? W ten sposób można uniknąć UNIONs i używać indeksów dla wszystkich danych.
dezso

1
@ypercube W rzeczywistości prawdopodobnie będą też zapytania bez warunku account_id . DISTINCT flaga to relikt z poprzedniej próbach i jest właściwie bezużyteczny, ponieważ wyniki będą się różnić i dlatego zawsze DISTINCT jest zachowanie dafualt. Tabele już istnieją i są wypełnione danymi. W każdym razie mogą wystąpić zmiany w układzie w zależności od źródła, dlatego chcę je podzielić. Ponadto klienci rejestrujący używają różnych poświadczeń z określonego powodu. Muszę zachować rodzaj warstwy między czytnikami dzienników a rzeczywistymi tabelami.
Lukas

OK, ale sprawdź, czy przejście na UNION ALLinny system daje inny plan wykonania.
ypercubeᵀᴹ

Odpowiedzi:


8

Z ciekawości możesz wypróbować tę wersję? Optymalizator może oszukać te same indeksy, których użyłyby podzapytania osobno:

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10) 
    AS a

UNION ALL

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)
    AS b

ORDER BY zeit DESC LIMIT 10;

Nadal uważam, że najlepszym indeksem, jaki możesz mieć, jest związek (account_id, zeitpunkt). Dałoby to szybko 10 rzędów i żadne sztuczki nie byłyby potrzebne.


Twoja modyfikacja przyniosła pożądane rezultaty. Dzięki! Na marginesie: do tej pory nie jestem pewien, który indeks będzie lepszy. Mógłbym nawet użyć obu. Będę musiał sprawdzić, jak log entries / userskaluje się liczba użytkowników i skala.
Lukas

Jeśli będziesz potrzebować zapytań zi zapytań bez account_id=?, zachowaj oba.
ypercubeᵀᴹ

@ypercube, +1 to jest bardzo sprytne i działało również w mojej (podobnej) sytuacji! Czy potrafisz wyjaśnić, dlaczego zawijanie ujednoliconych zapytań w atrapę oszuka SELECT * FROMMySQL w użyciu indeksów?
dkaminy

@dkamins: Optymalizator MySQL nie jest bardzo sprytny, zwykle gdy istnieje tabela pochodna, taka jak tutaj (SELECT ...) AS a, próbuje ona ocenić i zoptymalizować tabelę pochodną oddzielnie od innych tabel pochodnych, a następnie całe zapytanie.
ypercubeᵀᴹ

@Lukas, W rzeczywistości, ponieważ musisz upewnić się, że indeks jest używany, użycie / dodawanie force indexda ci lepsze rozwiązanie.
Pacerier
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.