Wydaje się, że wykonywanie pełnotekstowych zapytań w tej bazie danych (przechowywanie biletów RT ( Request Tracker )) trwa bardzo długo. Tabela załączników (zawierająca dane pełnotekstowe) ma około 15 GB.
Schemat bazy danych jest następujący, ma około 2 milionów wierszy:
rt4 = # \ d + załączniki Tabela „public.attachments” Kolumna | Wpisz | Modyfikatory | Przechowywanie | Opis ----------------- + ----------------------------- + - -------------------------------------------------- ------ + ---------- + ------------- id | liczba całkowita | not null default nextval ('attachments_id_seq' :: regclass) | zwykły | transakcja | liczba całkowita | nie zerowy | zwykły | rodzic | liczba całkowita | nie jest wartością domyślną 0 | zwykły | messageid | różniące się postaciami (160) | | rozszerzony | temat | różniące się postaciami (255) | | rozszerzony | nazwa pliku | różniące się postaciami (255) | | rozszerzony | typ zawartości | różniące się postaciami (80) | | rozszerzony | kodowanie zawartości | różniące się postaciami (80) | | rozszerzony | treść | tekst | | rozszerzony | nagłówki | tekst | | rozszerzony | twórca | liczba całkowita | nie jest wartością domyślną 0 | zwykły | utworzony | sygnatura czasowa bez strefy czasowej | | zwykły | contentindex | tsvector | | rozszerzony | Indeksy: „attachments_pkey” KLUCZ PODSTAWOWY, btree (id) „attachments1” btree (rodzic) „attachments2” btree (transactionid) „attachments3” btree (rodzic, transakcja) „contentindex_idx” gin (contentindex) Ma identyfikatory OID: nie
Mogę bardzo szybko wykonać zapytanie do bazy danych (<1s) za pomocą zapytania takiego jak:
select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');
Jednak gdy RT uruchamia zapytanie, które ma wykonać wyszukiwanie pełnotekstowe indeksu w tej samej tabeli, zwykle zajmuje setki sekund. Dane wyjściowe analizy zapytania są następujące:
Pytanie
SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);
EXPLAIN ANALYZE
wynik
PLAN ZAPYTANIA -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------- Agregat (koszt = 51210.60..51210.61 wierszy = 1 szerokość = 4) (czas rzeczywisty = 477778.806..477778.806 wierszy = 1 pętla = 1) -> Zagnieżdżona pętla (koszt = 0,00..51210,57 wierszy = 15 szerokości = 4) (czas rzeczywisty = 17943.986..477775.174 wierszy = 4197 pętli = 1) -> Zagnieżdżona pętla (koszt = 0,00..40643,08 wierszy = 6507 szerokość = 8) (czas rzeczywisty = 8,526..20610,380 wierszy = 1714818 pętli = 1) -> Seq Scan on bilety główne (koszt = 0,00..98188,37 wierszy = 598 szerokość = 8) (czas rzeczywisty = 0,008..256,042 wierszy = 96990 pętli = 1) Filtr: (((status) :: tekst „usunięty” :: tekst) ORAZ (id = efektywny) ORAZ ((typ) :: tekst = „bilet” :: tekst)) -> Skanowanie indeksu przy użyciu transakcji1 na transakcjach transakcyjnych_1 (koszt = 0,00..51,36 wierszy = 15 szerokości = 8) (rzeczywisty czas = 0,102..0,202 wierszy = 18 pętli = 96990) Indeks Cond: (((typ obiektu) :: text = 'RT :: Ticket' :: text) AND (objectid = main.id)) -> Indeksuj skanowanie za pomocą załączników2 na załącznikach attachments_2 (koszt = 0,00..1,61 wiersza = 1 szerokość = 4) (rzeczywisty czas = 0,266..0,266 wierszy = 0 pętli = 1714818) Indeks War: (transakcja = transakcje_1.id) Filtr: (contentindex @@ plainto_tsquery ('frobnicate' :: text)) Całkowity czas działania: 477778,883 ms
O ile mi wiadomo, wydaje się, że problem polega na tym, że nie używa on indeksu utworzonego w contentindex
polu ( contentindex_idx
), a raczej filtruje dużą liczbę pasujących wierszy w tabeli załączników. Liczby ANALYZE
wierszy w wynikach wyjaśniania również wydają się bardzo niedokładne, nawet po ostatnich : szacowane wiersze = 6507 rzeczywistych wierszy = 1714818.
Nie jestem do końca pewien, co dalej z tym zrobić.
Odpowiedzi:
Można to poprawić na tysiąc i jeden sposób, to powinno być kwestią milisekund .
Lepsze zapytania
To jest tylko twoje zapytanie sformatowane za pomocą aliasów i usuniętych szumów, aby usunąć mgłę:
Większość problemu ze swoimi kłamstwami zapytanie w pierwszych dwóch tabelach
tickets
itransactions
, które są brakujące z pytaniem. Wypełniam wykształcone domysły.t.status
,t.objecttype
itr.objecttype
prawdopodobnie nie powinno byćtext
, aleenum
ewentualnie jakaś bardzo mała wartość odnosząca się do tabeli przeglądowej.EXISTS
częściowo połączyćZakładając, że
tickets.id
jest to klucz podstawowy, ta przepisana forma powinna być znacznie tańsza:Zamiast mnożenia wierszy przez dwa sprzężenia 1: n, tylko w celu zwinięcia wielu dopasowań na końcu
count(DISTINCT id)
, użyj połączeniaEXISTS
częściowego, które może przestać szukać dalej, jak tylko zostanie znalezione pierwsze dopasowanie, a jednocześnie przestaje być ostatnimDISTINCT
krokiem. Według dokumentacji:Skuteczność zależy od liczby transakcji na bilet i załączników na transakcję.
Określ kolejność połączeń za pomocą
join_collapse_limit
Jeśli wiesz, że wyszukiwane hasło
attachments.contentindex
jest bardzo selektywne - bardziej selektywne niż inne warunki w zapytaniu (co prawdopodobnie ma miejsce w przypadku „frobnicate”, ale nie „problem”), możesz wymusić sekwencję złączeń. Narzędzie do planowania zapytań nie może ocenić selektywności poszczególnych słów, z wyjątkiem najczęściej używanych. Według dokumentacji:Użyj
SET LOCAL
tego celu, aby ustawić go tylko dla bieżącej transakcji.Kolejność
WHERE
warunków jest zawsze nieistotna. Znaczenie ma tylko kolejność dołączeń.Lub użyj CTE, takiego jak @jjanes, wyjaśniono w „Opcji 2”. dla podobnego efektu.
Indeksy
Indeksy B-drzewa
Podjąć wszelkie warunki, na
tickets
które są stosowane identycznie z większością zapytań i utworzyć częściowe indeksu natickets
:Jeśli jeden z warunków jest zmienny, usuń go z
WHERE
warunku i zamiast tego wstaw kolumnę jako kolumnę indeksu.Kolejny na
transactions
:Trzecia kolumna służy tylko do skanowania indeksów.
Ponadto, ponieważ masz ten indeks złożony z dwiema liczbami całkowitymi na
attachments
:Ten dodatkowy indeks jest kompletnym marnotrawstwem , usuń go:
Detale:
Indeks GIN
Dodaj
transactionid
do swojego indeksu GIN, aby był o wiele bardziej skuteczny. Może to być kolejna srebrna kula , ponieważ potencjalnie umożliwia skanowanie tylko za pomocą indeksu, całkowicie eliminując wizyty na dużym stole.Potrzebujesz dodatkowych klas operatorów zapewnianych przez dodatkowy moduł
btree_gin
. Szczegółowe instrukcje:4 bajty z
integer
kolumny nie zwiększają znacznie indeksu. Na szczęście dla Ciebie indeksy GIN różnią się od indeksów B-drzewa w kluczowym aspekcie. Według dokumentacji:Odważny nacisk moje. Potrzebujesz więc tylko jednego (dużego i nieco kosztownego) indeksu GIN.
Definicja tabeli
Przesuń
integer not null columns
do przodu. Ma to kilka niewielkich pozytywnych skutków dla przechowywania i wydajności. W tym przypadku oszczędza 4 - 8 bajtów na wiersz.źródło
opcja 1
Planista nie ma wglądu w prawdziwą naturę relacji między EffectiveId i id, więc prawdopodobnie myśli klauzulę:
będzie znacznie bardziej selektywna niż w rzeczywistości. Jeśli tak właśnie myślę, EffectiveID jest prawie zawsze równy main.id, ale planista tego nie wie.
Prawdopodobnie lepszym sposobem na przechowywanie tego typu relacji jest zwykle zdefiniowanie NULL wartości EffectiveID w celu oznaczenia „efektywnie taki sam jak identyfikator” i zapisanie czegoś w nim tylko w przypadku różnicy.
Zakładając, że nie chcesz reorganizować swojego schematu, możesz spróbować obejść go, przepisując tę klauzulę jako coś w rodzaju:
Planista może założyć, że
between
jest mniej selektywny niż równość, i to może wystarczyć, aby wyrzucić go z obecnej pułapki.Opcja 2
Innym podejściem jest użycie CTE:
Zmusza to planistę do korzystania z ContentIndex jako źródła selektywności. Gdy zostanie to do tego zmuszone, mylące korelacje kolumn w tabeli biletów nie będą już wyglądać tak atrakcyjnie. Oczywiście, jeśli ktoś wyszuka „problem” zamiast „frobnicate”, może to przynieść odwrót.
Opcja 3
Aby dalej zbadać szacunki złych wierszy, powinieneś uruchomić poniższe zapytanie we wszystkich 2 ^ 3 = 8 permutacjach różnych klauzul AND, które są komentowane. Pomoże to ustalić, skąd pochodzi zła ocena.
źródło