Co kwalifikuje „zbyt wiele żądań bazy danych” w kodzie?


17

To jest dyskusja, którą prowadzę i niektórzy z moich współpracowników sądzili, że przyjdę tutaj i zobaczę, czy uda się osiągnąć ogólny konsensus.

Zasadniczo sprowadza się do następujących 2 opinii na temat wywołań bazy danych: 1. Wykonaj jedno duże połączenie, aby uzyskać wszystko, co może być potrzebne do zmniejszenia liczby wywołań bazy danych 2. Wykonaj mniejsze oddzielne połączenia w oparciu o to, co jest wymagane, aby zmniejszyć rozmiar Wywołania DB

Szczególnie dotyczy to wspólnego kodu. Skorzystamy z przykładu klasy pracowniczej, ponieważ jest to dość proste.

Powiedzmy, że twoja klasa pracownika ma 10 atrybutów wartości (imię, nazwisko, wynajęty itp.), A następnie 2 atrybuty klasy ... 1 wskazujący klasę działu, a następnie 1 przełożonego, który wskazuje na inny obiekt pracownika.

W myśleniu nr 1 wykonałeś jedno połączenie, które zwraca dane pracownika, a także pola potrzebne do wypełnienia atrybutów działu i przełożonego ... lub przynajmniej pola najczęściej używane z tych podobiektów.

W myśleniu nr 2 najpierw wypełniasz obiekt pracownika, a następnie wypełniasz obiekty Departamentu i Inspektora tylko wtedy, gdy są one faktycznie wymagane.

Postawa 2 jest dość prosta ... zminimalizuj rozmiar żądań i liczbę obiektów bazy danych, które muszą zostać trafione za każdym razem, gdy zostanie wykonane jedno z tych żądań. # 1 polega na tym, że nawet gdyby można go poprawnie zaimplementować, sam fakt, że kod musiałby nawiązywać wiele połączeń, spowoduje większe obciążenie połączenia między serwerem internetowym a bazą danych, zamiast zmniejszać go.

Siłą napędową badań jest to, że ilość ruchu między naszym serwerem a serwerem bazy danych wymyka się spod kontroli.


7
Z mojego doświadczenia wynika, że ​​nie ma na to „właściwej odpowiedzi”. Istnieje równowaga między opóźnieniem a przepustowością. Małe opóźnienie może tolerować wiele małych żądań lub nawet jednego dużego; jednak łącza o wysokim opóźnieniu zwykle lepiej przenoszą wiele danych jednocześnie. Niemniej jednak, jeśli przepustowość jest niska w konfiguracji z dużym opóźnieniem, lepiej jest pobierać mniejsze porcje, aby być bardziej responsywnym.

3
Prawdopodobnie związany z problemem n + 1 stackoverflow.com/questions/97197/…
Valera Kolupaev

@Valera: dla wygody tutaj jest link zamieszczony na to pytanie: realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=n1selects
rwong

4
„ilość ruchu między naszym serwerem a serwerem bazy danych wymyka się spod kontroli”. Co to znaczy? Czy możesz sprecyzować, na czym polega prawdziwy problem? Czy masz problemy z wydajnością? Czy wykonałeś profilowanie i pomiary? Podaj rzeczywiste wyniki rzeczywistych pomiarów jako część pytania. W przeciwnym razie zgadujemy.
S.Lott,

Odpowiedzi:


8

Jeśli siłą napędową tego pytania jest zbyt duży ruch, czy sprawdziłeś buforowanie często używanych obiektów? Na przykład: po otrzymaniu obiektów pracownika i działu oraz przełożonego może warto dodać im pamięć podręczną, aby w razie potrzeby ponownie otrzymać je w niedalekiej przyszłości, są już w pamięci podręcznej i nie trzeba ich odzyskiwać jeszcze raz. Oczywiście pamięć podręczna będzie musiała pozwolić na wygaśnięcie rzadko używanych obiektów, a także musi być w stanie usunąć obiekty, które zostały zmodyfikowane przez aplikację i zapisane z powrotem w bazie danych.

W zależności od używanego języka i frameworka, może już istnieć struktura buforowania, która może wykonać niektóre (lub większość) z potrzebnych rzeczy. Jeśli używasz Javy, możesz zajrzeć do Apache Commons-Cache (nie używałem go od dłuższego czasu, i chociaż wygląda na uśpiony, nadal jest dostępny i był całkiem przyzwoity przy ostatnim użyciu).


3

Zawsze zwracaj uwagę na czytelność i przejrzystość przy pierwszym pisaniu. Następnie możesz refaktoryzować, jeśli i kiedy musisz. Wykonaj testy obciążenia, aby znaleźć wąskie gardła, w wielu przypadkach nie jest to liczba wywołań problemu, ale źle napisane.

Co do tego, co klasyfikuje jako zbyt wiele, zależy od aplikacji. W przypadku większości aplikacji internetowych wszystko poniżej 30 sekund jest prawie do zaakceptowania. Chciałbym porozmawiać z użytkownikami o ich oczekiwaniach.


Co stanowi źle napisane wywołanie db?
nve everest

3

Twoje pytanie wydaje się oparte na założeniu, że musisz zgadnąć, jakie dane będą potrzebne dla danej strony. To nie o to chodzi. Nie jest to tak proste, jak naiwne podejście, ale możesz tak zaprojektować swój kod, abyś wiedział, czy będziesz potrzebować atrybutów działu lub przełożonego przed wykonaniem jakichkolwiek wywołań bazy danych.


3

To są zasady, których używam, może ci się przydadzą.

  1. Zmierz pierwszy! Nie będę nawet patrzeć na kod, który „może być powolny”, chyba że faktycznie widzę ruch płynący do tego zasobu i ten zasób reaguje powoli.
  2. 1 żądanie = K zapytań. Liczba przypadków, w których rozmawiam z bazą danych, jest w pełni zależna od rodzaju żądanego zasobu; i nigdy ze względu na charakter żądania lub stan tego zasobu; W twoim przykładzie są to prawdopodobnie maksymalnie 3 zapytania: 1 dla pracowników, 1 dla działów i 1 dla przełożonych; Nie ma znaczenia, ilu z nich się zdarzy.
  3. Nie pytaj, czego nie będziesz używać . Jeśli mówimy o HTTP, nie ma sensu wyszukiwać danych później; nie ma później; każde żądanie zaczyna się od czystej listy. Czasami potrzebuję większości kolumn ze stołu, ale czasami potrzebuję tylko jednej lub dwóch; kiedy dokładnie wiem, jakich pól potrzebuję, poproszę właśnie o to.
  4. Rzuć sprzęt na problem. Serwery są tanie; Czasami można uzyskać wystarczającą wydajność, po prostu przenosząc bazę danych do bardziej rozbudowanego pudełka; lub wysyłając zapytania do repliki tylko do odczytu.
  5. Najpierw unieważnij pamięć podręczną, a następnie zaimplementuj buforowanie. Silna jest potrzeba umieszczania często używanych lub trudnych do przeszukiwania danych w pamięci podręcznej; ale zbyt często eksmitowanie nieużywanych danych lub wygasanie zastąpionych danych jest pomijane. Jeśli wiesz, jak wyjąć dane z pamięci podręcznej; wtedy możesz bezpiecznie umieścić go w pamięci podręcznej; Jeśli okaże się, że droższe jest unieważnienie pamięci podręcznej niż samo wykonanie zapytania; wtedy nie potrzebowałeś pamięci podręcznej.

2

Obie strategie tutaj są całkowicie poprawne. Każda ma swoje zalety i wady:

Jedno wywołanie dla wszystkich 3 obiektów:

  • wykona się szybciej
  • dostaniesz dokładnie to, czego potrzebujesz w przypadku, gdy tego potrzebujesz
  • prawdopodobnie będzie przydatny tylko w jednym przypadku (może to być jednak bardzo częsty przypadek)
  • będzie trudniej utrzymać
  • będzie musiał być utrzymywany częściej (ponieważ zmieni się, jeśli którykolwiek ze schematów 3 obiektów lub konieczna zmiana danych)

Jedno wywołanie na obiekt (łącznie 3 połączenia)

  • Daje ci wywołanie ogólnego przeznaczenia, aby wypełnić jedną instancję każdego typu obiektu; mogą być następnie używane niezależnie
  • Będzie łatwiejsza w utrzymaniu, ponieważ struktura zapytań będzie prostsza.
  • Będzie wolniejszy (niekoniecznie 3 razy wolniejszy, ale dla tych samych danych zwiększa się koszty ogólne)
  • Może powodować problemy z odzyskiwaniem niepotrzebnych danych (ciągnięcie całego rekordu, gdy potrzebujesz jednego pola, jest marnotrawstwem)
  • Może powodować problemy z N + 1, gdy istnieje relacja wiele do jednego, jeśli zapytanie dotyczące jednego rekordu jest wysyłane N razy, jeden na rekord w kolekcji.

W odpowiedzi na kilka twoich obaw (nr 3 i 5 na drugiej liście) ... Co zrobić, jeśli Kierownik i Departament są wykorzystywane tylko w 1/3 (lub mniej) czasie? Co zrobić, jeśli kod został zaprojektowany w taki sposób, aby uzyskać wszystkie dzieci, gdy tylko odwołano się do obiektu List <> zakodowanego w celu ich zawarcia? ... czy to złagodziłoby większość ostrożności?
user107775,

Jeśli obiekty pomocnicze są rzadko potrzebne, wówczas w ogólnym przypadku będzie to działało szybciej (mniej danych do pobrania), ale najgorsze będzie wolniejsze (te same dane lub więcej, przy trzykrotnym obciążeniu komunikacyjnym z komputera). Jeśli chodzi o problem N + 1, po prostu musisz być w stanie zaprojektować zapytanie, które pobiera listę obiektów, aby móc to zrobić na podstawie klucza obcego po „jednej” stronie relacji, a następnie wyciągnąć wiele wierszy z wyniku zapytania. Nie można użyć wersji zapytania, która musi mieć klucz podstawowy rekordu.
KeithS

1

Dla mnie zbyt wiele żądań DB generuje więcej żądań, niż trzeba załadować potrzebne dane w danym momencie.

Więc nie potrzebujesz danych, nie marnuj pamięci na jej zdobycie, aby uniknąć drugiej podróży później. Ale jeśli potrzebujesz ilości danych, powinieneś zminimalizować połączenia z bazą danych.

Miej więc obie opcje i używaj każdej z nich, gdy wymaga tego sytuacja.

EDYCJA: Pamiętaj, że to oczywiście zależy również od twojej sytuacji. Jeśli jest to na przykład aplikacja internetowa, należy mieć inne względy niż w przypadku aplikacji komputerowej uzyskującej dostęp do bazy danych w sieci, w przeciwieństwie do aplikacji WepApp w Internecie.


Co w przypadku, gdy piszesz wspólny kod i nie masz pewności, w jaki sposób Twój kod zostanie wykorzystany. Być może nigdy nie wyobrażasz sobie, że ktoś nie potrzebuje opiekuna, ale okazuje się, że aplikacja, nad którą pracujesz, jest jedyną, która tego potrzebuje. Jasne, możesz napisać osobne funkcje ... jedną, aby jej nie włączyć, a drugą, aby ją uwzględnić, ale w którym momencie twój wspólny kod zaczyna wymagać zbyt dużej szczegółowej wiedzy, aby go użyć?
user107775,

@ user107775 Zwykle piszę tylko dwie funkcje dla każdego przypadku; jeden, który zwraca tylko wartości właściwości, i drugi, który zwraca klasę ze wszystkimi powiązanymi klasami. Wynika to z tego, że W większości przypadków potrzebujesz tylko właściwości W ten sposób nie potrzebujesz szczegółowej wiedzy, tylko jeden dostaje podstawy, a drugi wszystko. Uważam to za rozsądną równowagę. (Jednak niektóre szczególne przypadki wymagają większej optymalizacji, ale dzieje się tak w poszczególnych przypadkach).
AJC

1

Połącz się z DB, wysyłaj zapytanie i parsuj je zwykle zajmuje dużo czasu w porównaniu do pobierania wyników, więc ogólną tendencją jest łączenie jak największej liczby zapytań w jednym żądaniu.

Mimo to zrobienie tego wszystkiego w jednym ujęciu sprawi, że kod będzie niemożliwy do utrzymania. Zamiast tego jest to zwykle osiągane przez dodatkową warstwę abstrakcji: kod planuje kilka żądań, gdy są one potrzebne, następnie silnik analizuje to jako jedno duże żądanie (możliwe, że używa po drodze pamięci podręcznej), a następnie odpowiedzi są wysyłane w razie potrzeby.

Oczywiście nie zawsze wszystko można pobrać w jednym zapytaniu - często masz zapytanie, które zawiera dane niezbędne do zbudowania następnego zapytania, więc musisz je powtórzyć. Wciąż oszałamiające pakiety zapytań i wykonywanie jak największej liczby na raz jest lepsze niż setki małych ujęć w bazie danych.

Zaplanuj więc, czego potrzebujesz, zażądaj i odzyskaj, jeśli potrzebujesz więcej, poproś i pobierz je ponownie, a następnie wykorzystaj dane do generowania treści. Zdecydowanie unikaj używania żądań bazy danych, takich jak inicjalizacja zmiennych lokalnych rozrzuconych po całym kodzie.


1

Nie wiemy wystarczająco dużo o Twojej aplikacji, aby wiedzieć, który wybór należy zoptymalizować zbyt wcześnie. Jak często wykorzystywane są dane Inspektora? Wygląda na to, że może to być strata, ale nie wiemy. Jeśli je rozdzielisz, być może będziesz w stanie monitorować swój system, aby zobaczyć, jak często kończą się one razem. Następnie możesz podjąć decyzję o połączeniu ich w jednym połączeniu. W przeciwnym razie, jeśli zaczniesz tworzyć szyjkę butelki za pomocą tego jednego dużego wezwania, gdzie zaczniesz rozwiązywać problemy? Trudno zidentyfikować, co warto pominąć. Więcej pól danych może zostać dodanych do tego procesu.

Interesujące byłoby wiedzieć, ile z tego pochodzi z pamięci db vs dysk. Nie ma nic, co sprawiłoby, żebym poczuł, że dział jest mniej lub bardziej zmieniony w porównaniu do adresu.

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.