{"id":35259,"date":"2019-10-31T22:03:17","date_gmt":"2019-10-31T19:03:17","guid":{"rendered":"https:\/\/prohoster.info\/blog\/istoriya-odnogo-sql-rassledovaniya\/"},"modified":"2019-10-31T22:03:17","modified_gmt":"2019-10-31T19:03:17","slug":"istoriya-odnogo-sql-rassledovaniya","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","title":{"rendered":"Historia jednego \u015bledztwa SQL","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>W grudniu ubieg\u0142ego roku otrzyma\u0142em interesuj\u0105cy raport o b\u0142\u0119dzie od zespo\u0142u wsparcia VWO. Czas \u0142adowania jednego z raport\u00f3w analitycznych dla du\u017cego klienta korporacyjnego wydawa\u0142 si\u0119 nieproporcjonalnie d\u0142ugi. Poniewa\u017c to le\u017ca\u0142o w moich kompetencjach, od razu skupi\u0142em si\u0119 na rozwi\u0105zaniu problemu.<\/p>\n<p><\/p>\n<h2>T\u0142o<\/h2>\n<p><\/p>\n<p>Aby wyja\u015bni\u0107, o co chodzi, opowiem kr\u00f3tko o VWO. To platforma, kt\u00f3ra umo\u017cliwia prowadzenie r\u00f3\u017cnych ukierunkowanych kampanii na swoich stronach: przeprowadzanie eksperyment\u00f3w A\/B, \u015bledzenie odwiedzaj\u0105cych i konwersji, analizowanie lejk\u00f3w sprzeda\u017cowych, wy\u015bwietlanie map cieplnych oraz odtwarzanie nagra\u0144 wizyt.<\/p>\n<p><\/p>\n<p>Ale najwa\u017cniejsz\u0105 funkcj\u0105 platformy jest tworzenie raport\u00f3w. Wszystkie wymienione funkcje s\u0105 ze sob\u0105 powi\u0105zane. Dla klient\u00f3w korporacyjnych ogromna ilo\u015b\u0107 informacji by\u0142aby po prostu bezu\u017cyteczna bez solidnej platformy, kt\u00f3ra przedstawia je w formacie analitycznym.<\/p>\n<p><\/p>\n<p>Korzystaj\u0105c z platformy, mo\u017cna z\u0142o\u017cy\u0107 dowolne zapytanie na du\u017cym zbiorze danych. Oto prosty przyk\u0142ad:<\/p>\n<p><\/p>\n<pre>Poka\u017c wszystkie klikni\u0119cia na stronie \"abc.com\" OD  DO  dla os\u00f3b, kt\u00f3re u\u017cywa\u0142y Chrome LUB (by\u0142y w Europie I u\u017cywa\u0142y iPhone'a)<\/pre>\n<p><\/p>\n<p>Zauwa\u017c, \u017ce operatory logiczne s\u0105 dost\u0119pne dla klient\u00f3w w interfejsie zapytania, aby tworzy\u0107 dowolnie skomplikowane zapytania do pozyskiwania pr\u00f3bek.<\/p>\n<p><\/p>\n<h2>Wolne zapytanie<\/h2>\n<p><\/p>\n<p>Klient, o kt\u00f3rym mowa, pr\u00f3bowa\u0142 zrobi\u0107 co\u015b, co intuicyjnie powinno dzia\u0142a\u0107 szybko:<\/p>\n<p><\/p>\n<pre>Poka\u017c wszystkie nagrania sesji dla u\u017cytkownik\u00f3w, kt\u00f3rzy odwiedzili dowoln\u0105 stron\u0119 z URL-em zawieraj\u0105cym \"\\\/jobs\"<\/pre>\n<p><\/p>\n<p>Na tej stronie by\u0142o ogromne nat\u0119\u017cenie ruchu, a my przechowywali\u015bmy ponad milion unikalnych adres\u00f3w URL tylko dla niej. A oni chcieli znale\u017a\u0107 do\u015b\u0107 prosty wz\u00f3r URL-a, odnosz\u0105cy si\u0119 do ich modelu biznesowego.<\/p>\n<p>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Wst\u0119pne dochodzenie<\/h2>\n<p><\/p>\n<p>Sp\u00f3jrzmy, co si\u0119 dzieje w bazie danych. Poni\u017cej znajduje si\u0119 oryginalne powolne zapytanie SQL:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT \n    count(*) \nFROM \n    acc_{account_id}.urls as recordings_urls, \n    acc_{account_id}.recording_data as recording_data, \n    acc_{account_id}.sessions as sessions \nWHERE \n    recording_data.usp_id = sessions.usp_id \n    AND sessions.referrer_id = recordings_urls.id \n    AND  (  urls &amp;&amp; array(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com\/jobs%')::text[] ) \n    AND r_time &gt; to_timestamp(1542585600) \n    AND r_time = 5 \n    AND recording_data.num_of_pages &gt; 0 ;<\/code><\/pre>\n<p><\/p>\n<p>A oto czasy:<\/p>\n<p><\/p>\n<pre>Planowany czas: 1,480 ms\nCzas realizacji: 1431924,650 ms<\/pre>\n<p><\/p>\n<p>Zapytanie obejmowa\u0142o 150 tysi\u0119cy wierszy. Planner zapyta\u0144 ujawni\u0142 kilka interesuj\u0105cych szczeg\u00f3\u0142\u00f3w, ale nie pokaza\u0142 \u017cadnych oczywistych w\u0105skich garde\u0142.<\/p>\n<p><\/p>\n<p>Przyjrzyjmy si\u0119 temu zapytaniu bardziej szczeg\u00f3\u0142owo. Jak wida\u0107, wykonuje ono <code>JOIN<\/code> trzy tabele:<\/p>\n<p><\/p>\n<ol>\n<li><strong>sessions<\/strong>: do wy\u015bwietlania informacji o sesjach: przegl\u0105darka, agent u\u017cytkownika, kraj itd.<\/li>\n<li><strong>recording_data<\/strong>: zapisane URL-e, strony, czas trwania wizyt<\/li>\n<li><strong>urls<\/strong>: aby unikn\u0105\u0107 duplikacji niezwykle du\u017cych URL-i, przechowujemy je w osobnej tabeli.<\/li>\n<\/ol>\n<p><\/p>\n<p>Zwr\u00f3\u0107 uwag\u0119, \u017ce wszystkie nasze tabele s\u0105 ju\u017c podzielone wed\u0142ug <code>account_id<\/code>. W ten spos\u00f3b wyklucza si\u0119 sytuacj\u0119, w kt\u00f3rej z powodu jednego szczeg\u00f3lnie du\u017cego konta problemy wyst\u0119puj\u0105 u innych.<\/p>\n<p><\/p>\n<h2>W poszukiwaniu dowod\u00f3w<\/h2>\n<p><\/p>\n<p>Przy bli\u017cszym przyjrzeniu si\u0119 widzimy, \u017ce co\u015b jest nie tak z konkretnym zapytaniem. Warto zwr\u00f3ci\u0107 uwag\u0119 na ten wiersz:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">urls &amp;&amp; array(\n\tselect id from acc_{account_id}.urls \n\twhere url ILIKE '%enterprise_customer.com\/jobs%'\n)::text[]<\/code><\/pre>\n<p><\/p>\n<p>Pierwsz\u0105 my\u015bl\u0105 by\u0142o, \u017ce by\u0107 mo\u017ce z powodu <code>ILIKE<\/code> na tych wszystkich d\u0142ugich URL-ach (mamy ponad 1,4 miliona <strong>unikalnych\u00a0<\/strong>adres\u00f3w URL zgromadzonych dla tego konta) wydajno\u015b\u0107 mo\u017ce by\u0107 niska.<\/p>\n<p><\/p>\n<p>Ale nie, to nie o to chodzi!<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT id FROM urls WHERE url ILIKE '%enterprise_customer.com\/jobs%';\n  id\n--------\n ...\n(198661 wierszy)\n\nCzas: 5231.765 ms<\/code><\/pre>\n<p><\/p>\n<p>Same zapytanie wyszukiwania wzorca zajmuje zaledwie 5 sekund. Wyszukiwanie wzorca w milionie unikalnych URL-i zdecydowanie nie stanowi problemu.<\/p>\n<p><\/p>\n<p>Nast\u0119pny podejrzany na li\u015bcie to kilka <code>JOIN<\/code>. By\u0107 mo\u017ce ich nadmierne u\u017cycie prowadzi do spowolnienia? Zwykle <code>JOIN<\/code>Ram \u2014 to najbardziej oczywisty kandydat na problemy z wydajno\u015bci\u0105, ale nie wierzy\u0142em, \u017ce w naszym przypadku tak jest.<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">analytics_db=# SELECT\n    count(*)\nFROM\n    acc_{account_id}.urls as recordings_urls,\n    acc_{account_id}.recording_data_0 as recording_data,\n    acc_{account_id}.sessions_0 as sessions\nWHERE\n    recording_data.usp_id = sessions.usp_id\n    AND sessions.referrer_id = recordings_urls.id\n    AND r_time &gt; to_timestamp(1542585600)\n    AND r_time = 5\n    AND recording_data.num_of_pages &gt; 0 ;\n count\n-------\n  8086\n(1 wiersz)\n\nCzas: 147.851 ms<\/code><\/pre>\n<p><\/p>\n<p>I to r\u00f3wnie\u017c nie by\u0142 nasz przypadek. <code>JOIN<\/code>Ram okaza\u0142 si\u0119 bardzo szybki.<\/p>\n<p><\/p>\n<h2>Zaw\u0119\u017camy kr\u0105g podejrzanych<\/h2>\n<p><\/p>\n<p>By\u0142em got\u00f3w zacz\u0105\u0107 zmienia\u0107 zapytanie, aby osi\u0105gn\u0105\u0107 jakiekolwiek mo\u017cliwe poprawki wydajno\u015bci. My i zesp\u00f3\u0142 opracowali\u015bmy 2 g\u0142\u00f3wne pomys\u0142y:<\/p>\n<p><\/p>\n<ul>\n<li><strong>U\u017cy\u0107 EXISTS dla podzapytania URL<\/strong>: Chcieli\u015bmy jeszcze raz sprawdzi\u0107, czy s\u0105 jakie\u015b problemy z podzapytaniem dla URL-i. Jednym ze sposob\u00f3w, aby to osi\u0105gn\u0105\u0107, jest po prostu u\u017cycie <code>EXISTS<\/code>. <code>EXISTS<\/code> <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS\">mo\u017ce<\/a><\/noindex> znacz\u0105co poprawi\u0107 wydajno\u015b\u0107, poniewa\u017c ko\u0144czy si\u0119 natychmiast, gdy znajdzie jedn\u0105 lini\u0119 zgodnie z warunkiem.<\/li>\n<\/ul>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT\n\tcount(*) \nFROM \n    acc_{account_id}.urls as recordings_urls,\n    acc_{account_id}.recording_data as recording_data,\n    acc_{account_id}.sessions as sessions\nWHERE\n    recording_data.usp_id = sessions.usp_id\n    AND  (  1 = 1  )\n    AND sessions.referrer_id = recordings_urls.id\n    AND  (exists(select id from acc_{account_id}.urls where url  ILIKE '%enterprise_customer.com\/jobs%'))\n    AND r_time &gt; to_timestamp(1547585600)\n    AND r_time =5\n    AND recording_data.num_of_pages &gt; 0 ;\n count\n 32519\n(1 row)\nTime: 1636.637 ms<\/code><\/pre>\n<p><\/p>\n<p>Tak. Podzapytanie, gdy jest owini\u0119te w\u00a0<code>EXISTS<\/code>, sprawia, \u017ce wszystko dzia\u0142a super szybko. Nast\u0119pne logiczne pytanie to, dlaczego zapytanie z <code>JOIN<\/code>-ami i samo podzapytanie s\u0105 szybkie z osobna, ale wolno dzia\u0142aj\u0105 razem?<\/p>\n<p><\/p>\n<ul>\n<li><strong>Przenosimy podzapytanie do CTE <\/strong>: je\u015bli zapytanie jest szybkie samo w sobie, mo\u017cemy najpierw obliczy\u0107 szybki wynik, a nast\u0119pnie przekaza\u0107 go do g\u0142\u00f3wnego zapytania<\/li>\n<\/ul>\n<p><\/p>\n<pre><code class=\"plaintext\">WITH matching_urls AS (\n    select id::text from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com\/jobs%'\n)\n\nSELECT \n    count(*) FROM acc_{account_id}.urls as recordings_urls, \n    acc_{account_id}.recording_data as recording_data, \n    acc_{account_id}.sessions as sessions,\n    matching_urls\nWHERE \n    recording_data.usp_id = sessions.usp_id \n    AND  (  1 = 1  )  \n    AND sessions.referrer_id = recordings_urls.id\n    AND (urls &amp;&amp; array(SELECT id from matching_urls)::text[])\n    AND r_time &gt; to_timestamp(1542585600) \n    AND r_time =5 \n    AND recording_data.num_of_pages &gt; 0;<\/code><\/pre>\n<p><\/p>\n<p>Ale to nadal by\u0142o bardzo wolne.<\/p>\n<p><\/p>\n<h2>Szukamy winowajcy<\/h2>\n<p><\/p>\n<p>Ca\u0142y czas przed oczyma miga\u0142 jeden szczeg\u00f3\u0142, od kt\u00f3rego wci\u0105\u017c odsuwa\u0142em si\u0119. Ale poniewa\u017c nie mia\u0142em ju\u017c nic, postanowi\u0142em na niego spojrze\u0107. M\u00f3wi\u0119 o <code>&amp;&amp;<\/code> operatorze. Dop\u00f3ki <code>EXISTS<\/code> po prostu poprawi\u0142 wydajno\u015b\u0107, <code>&amp;&amp;<\/code> by\u0142 jedynym wsp\u00f3lnym czynnikiem we wszystkich wersjach wolnego zapytania.<\/p>\n<p><\/p>\n<p>Patrz\u0105c na <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/9.1\/functions-array.html\">dokumentacj\u0119<\/a><\/noindex>, widzimy, \u017ce <code>&amp;&amp;<\/code> jest u\u017cywany, gdy trzeba znale\u017a\u0107 wsp\u00f3lne elementy mi\u0119dzy dwoma tablicami.<\/p>\n<p><\/p>\n<p>W oryginalnym zapytaniu to:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">AND  (  urls &amp;&amp;  array(select id from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com\/jobs%')::text[]   )<\/code><\/pre>\n<p><\/p>\n<p>Co oznacza, \u017ce robimy wyszukiwanie wzorca w naszych URL-ach, a nast\u0119pnie znajdujemy przeci\u0119cie ze wszystkimi URL-ami z og\u00f3lnymi zapisami. To jest troch\u0119 myl\u0105ce, poniewa\u017c \u201eurls\u201d tutaj nie odnosi si\u0119 do tabeli, kt\u00f3ra zawiera wszystkie adresy URL, lecz do kolumny \u201eurls\u201d w tabeli <code>recording_data<\/code>.<\/p>\n<p><\/p>\n<p>W miar\u0119 wzrastania podejrze\u0144 dotycz\u0105cych <code>&amp;&amp;<\/code>, pr\u00f3bowa\u0142em znale\u017a\u0107 potwierdzenie w planie zapytania, kt\u00f3ry zosta\u0142 wygenerowany <code>EXPLAIN ANALYZE<\/code> (mia\u0142em ju\u017c zapisany plan, ale zazwyczaj wygodniej jest mi eksperymentowa\u0107 w SQL, ni\u017c pr\u00f3bowa\u0107 zrozumie\u0107 nieprzezroczysto\u015bci planist\u00f3w zapyta\u0144).<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Filtr: ((adresy URL &amp;&amp; ($0)::text[]) I (r_time &gt; '2018-12-17 12:17:23+00'::timestamp with time zone) I (r_time = '5'::double precision) I (liczba_stron &gt; 0))\\n                           Wiersze usuni\u0119te przez filtr: 52710<\/code><\/pre>\n<p><\/p>\n<p>By\u0142o tam kilka wierszy filtr\u00f3w tylko z <code>&amp;&amp;<\/code>. Co oznacza\u0142o, \u017ce ta operacja by\u0142a nie tylko kosztowna, ale tak\u017ce wykonywana wielokrotnie.<\/p>\n<p><\/p>\n<p>Sprawdzi\u0142em to, izoluj\u0105c warunek<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT 1\\nFROM \\n    acc_{account_id}.urls jako recordings_urls, \\n    acc_{account_id}.recording_data_30 jako recording_data_30, \\n    acc_{account_id}.sessions_30 jako sessions_30 \\nGDZIE \\n\\turls &amp;&amp;  array(select id from acc_{account_id}.urls where url  ILIKE  '%enterprise_customer.com\/jobs%')::text[]<\/code><\/pre>\n<p><\/p>\n<p>To zapytanie dzia\u0142a\u0142o wolno. Poniewa\u017c <code>JOIN<\/code>-y s\u0105 szybkie, a podzapytania s\u0105 szybkie, pozostawa\u0142 tylko <code>&amp;&amp;<\/code> operator.<\/p>\n<p><\/p>\n<p>Ale to kluczowa operacja. Zawsze musimy przeszukiwa\u0107 ca\u0142\u0105 g\u0142\u00f3wn\u0105 tabel\u0119 adres\u00f3w URL w celu wyszukiwania wed\u0142ug wzoru, a zawsze musimy znajdowa\u0107 przeci\u0119cia. Nie mo\u017cemy przeszukiwa\u0107 bezpo\u015brednio po wpisach adres\u00f3w URL, poniewa\u017c s\u0105 to tylko identyfikatory odnosz\u0105ce si\u0119 do <code>urls<\/code>.<\/p>\n<p><\/p>\n<h2>W drodze do rozwi\u0105zania<\/h2>\n<p><\/p>\n<p><code>&amp;&amp;<\/code> wolna, poniewa\u017c oba zestawy s\u0105 ogromne. Operacja b\u0119dzie stosunkowo szybka, je\u015bli zast\u0105pi\u0119 <code>urls<\/code> na <code>{ \"http:\/\/google.com\/\", \"http:\/\/wingify.com\/\" }<\/code>.<\/p>\n<p><\/p>\n<p>Zacz\u0105\u0142em szuka\u0107 sposobu na wykonanie w Postgres przeci\u0119cia zbior\u00f3w bez u\u017cywania <code>&amp;&amp;<\/code>, ale bez wi\u0119kszego sukcesu.<\/p>\n<p><\/p>\n<p>W ko\u0144cu postanowili\u015bmy po prostu rozwi\u0105za\u0107 problem w izolacji: daj mi wszystkie <code>urls<\/code> linie, dla kt\u00f3rych adres URL pasuje do wzoru. Bez dodatkowych warunk\u00f3w to b\u0119dzie \u2014\u00a0<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT urls.url\\nFROM \\n\\tacc_{account_id}.urls jako urls,\\n\\t(SELECT unnest(recording_data.urls) AS id) AS unrolled_urls\\nGDZIE\\n\\turls.id = unrolled_urls.id I\\n\\turls.url  ILIKE  '%jobs%'<\/code><\/pre>\n<p><\/p>\n<p>Zamiast\u00a0<code>JOIN<\/code> syntaktyka, po prostu u\u017cy\u0142em podzapytania i rozwin\u0105\u0142em <code>recording_data.urls<\/code> tablic\u0119, aby mo\u017cna by\u0142o bezpo\u015brednio stosowa\u0107 warunek w <code>WHERE<\/code>.<\/p>\n<p><\/p>\n<p>Najwa\u017cniejsze tutaj to, \u017ce <code>&amp;&amp;<\/code> jest u\u017cywane do sprawdzenia, czy dany wpis zawiera odpowiadaj\u0105cy adres URL. Patrz\u0105c nieco bardziej uwa\u017cnie, mo\u017cna dostrzec w tej operacji przechodzenie przez elementy tablicy (lub wiersze tabeli) i zatrzymanie si\u0119 przy spe\u0142nieniu warunku (zgodno\u015bci). Przypomina to co\u015b? Aha, <code>EXISTS<\/code>.<\/p>\n<p><\/p>\n<p>Poniewa\u017c na <code>recording_data.urls<\/code> mo\u017cna odnosi\u0107 si\u0119 zewn\u0119trznie do kontekstu podzapytania, gdy to ma miejsce, mo\u017cemy wr\u00f3ci\u0107 do naszego starego znajomego <code>EXISTS<\/code> i owin\u0105\u0107 go podzapytaniem.<\/p>\n<p><\/p>\n<p>\u0141\u0105cz\u0105c wszystko razem, otrzymujemy ostateczne zoptymalizowane zapytanie:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">WYBIERZ \n    count(*) \nZ \n    acc_{account_id}.urls jako recordings_urls, \n    acc_{account_id}.recording_data jako recording_data, \n    acc_{account_id}.sessions jako sessions \nGDZIE \n    recording_data.usp_id = sessions.usp_id \n    I (  1 = 1  )  \n    I sessions.referrer_id = recordings_urls.id \n    I r_time &gt; to_timestamp(1542585600) \n    I r_time = 5 \n    I recording_data.num_of_pages &gt; 0\n    I ISTNIEJE(\n        WYBIERZ urls.url\n        Z \n            acc_{account_id}.urls jako urls,\n            (WYBIERZ unnest(urls) JAKO rec_url_id Z acc_{account_id}.recording_data) \n            AS unrolled_urls\n        GDZIE\n            urls.id = unrolled_urls.rec_url_id I\n            urls.url  ILIKE  '%enterprise_customer.com\\\/jobs%'\n    );\n<\/code><\/pre>\n<p><\/p>\n<p>I ostateczny czas wykonania <code>Czas: 1898.717 ms<\/code> Czas na \u015bwi\u0119towanie?!?<\/p>\n<p><\/p>\n<p>Nie tak szybko! Najpierw musimy sprawdzi\u0107 poprawno\u015b\u0107. By\u0142em bardzo sceptyczny wobec <code>EXISTS<\/code> optymalizacji, poniewa\u017c zmienia ona logik\u0119 na wcze\u015bniejsze zako\u0144czenie. Musimy upewni\u0107 si\u0119, \u017ce nie dodali\u015bmy \u017cadnego ukrytego b\u0142\u0119du do zapytania.<\/p>\n<p><\/p>\n<p>Prosta weryfikacja polega\u0142a na wykonaniu <code>count(*)<\/code> zar\u00f3wno w wolnych, jak i szybkich zapytaniach dla r\u00f3\u017cnych zestaw\u00f3w danych. Nast\u0119pnie, dla ma\u0142ego podzbioru danych, sprawdzi\u0142em poprawno\u015b\u0107 wszystkich wynik\u00f3w r\u0119cznie.<\/p>\n<p><\/p>\n<p>Wszystkie kontrole da\u0142y stabilnie pozytywne wyniki. Wszystko naprawili\u015bmy!<\/p>\n<p><\/p>\n<h2>Wyci\u0105gni\u0119te Wnioski<\/h2>\n<p><\/p>\n<p>Z tej historii mo\u017cna wyci\u0105gn\u0105\u0107 wiele lekcji:<\/p>\n<p><\/p>\n<ol>\n<li>Plany zapyta\u0144 nie m\u00f3wi\u0105 ca\u0142ej historii, ale mog\u0105 dawa\u0107 wskaz\u00f3wki<\/li>\n<li>G\u0142\u00f3wni podejrzani nie zawsze s\u0105 prawdziwymi winowajcami<\/li>\n<li>Wolne zapytania mo\u017cna podzieli\u0107, aby zlokalizowa\u0107 w\u0105skie gard\u0142a<\/li>\n<li>Nie wszystkie optymalizacje s\u0105 z natury redukcyjne<\/li>\n<li>U\u017cycie <code>EXIST<\/code>, gdzie to mo\u017cliwe, mo\u017ce prowadzi\u0107 do znacznego wzrostu wydajno\u015bci<\/li>\n<\/ol>\n<p><\/p>\n<h2>Wnioski<\/h2>\n<p><\/p>\n<p>Przeszli\u015bmy od czasu zapytania wynosz\u0105cego ~24 minuty do 2 sekund \u2014 to znaczny wzrost wydajno\u015bci! Chocia\u017c ten artyku\u0142 jest obszerny, wszystkie eksperymenty, kt\u00f3re przeprowadzili\u015bmy, mia\u0142y miejsce w ci\u0105gu jednego dnia i szacunkowo zaj\u0119\u0142y od 1,5 do 2 godzin na optymalizacj\u0119 i testowanie.<\/p>\n<p><\/p>\n<p>SQL to wspania\u0142y j\u0119zyk, je\u015bli si\u0119 go nie boicie, a spr\u00f3bujecie pozna\u0107 i wykorzysta\u0107. Posiadaj\u0105c dobre zrozumienie, jak wykonuj\u0105 si\u0119 zapytania SQL, jak BD generuje plany zapyta\u0144, jak dzia\u0142aj\u0105 indeksy i po prostu rozmiar danych, z kt\u00f3rymi si\u0119 macie do czynienia, mo\u017cecie bardzo skutecznie optymalizowa\u0107 zapytania. Niemniej jednak r\u00f3wnie wa\u017cne jest, aby nadal pr\u00f3bowa\u0107 r\u00f3\u017cnych podej\u015b\u0107 i stopniowo rozwi\u0105zywa\u0107 problemy, znajduj\u0105c w\u0105skie gard\u0142a.<\/p>\n<p><\/p>\n<p>Najlepsz\u0105 cz\u0119\u015bci\u0105 osi\u0105gania takich wynik\u00f3w jest widoczne, zauwa\u017calne poprawienie szybko\u015bci dzia\u0142ania \u2013 raport, kt\u00f3ry wcze\u015bniej nawet si\u0119 nie \u0142adowa\u0142, teraz \u0142aduje si\u0119 niemal natychmiast.<\/p>\n<p><\/p>\n<p><strong>Szczeg\u00f3lne podzi\u0119kowania\u00a0<\/strong>moim towarzyszom\u00a0<em>z zespo\u0142u Aditya Mishra<\/em>,\u00a0<em>Aditya Gaur\u00a0<\/em>i\u00a0<em><noindex><a rel=\"nofollow\" href=\"https:\/\/twitter.com\/s0ftvar\">Varun Malhotra\u00a0<\/a><\/noindex><\/em>za burz\u0119 m\u00f3zg\u00f3w i\u00a0<em>Dinkarowi Pandirze\u00a0<\/em>za wykrycie wa\u017cnego b\u0142\u0119du w naszym ko\u0144cowym zapytaniu, zanim ostatecznie si\u0119 z nim po\u017cegnali\u015bmy!<\/p>\n<p>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/455832\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412 \u0434\u0435\u043a\u0430\u0431\u0440\u0435 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u043e\u0442\u0447\u0435\u0442 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u043e\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b\u00a0\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 VWO. \u0412\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0442\u0447\u0435\u0442\u043e\u0432 \u0434\u043b\u044f \u043a\u0440\u0443\u043f\u043d\u043e\u0433\u043e \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u043f\u043e\u043c\u0435\u0440\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u043c. \u0410 \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e \u0441\u0444\u0435\u0440\u0430 \u043c\u043e\u0435\u0439 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438, \u044f \u0442\u0443\u0442 \u0436\u0435 \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0438\u043b\u0441\u044f \u043d\u0430 \u0440\u0435\u0448\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b. \u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0427\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e \u043e \u0447\u0451\u043c \u0440\u0435\u0447\u044c, \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u0441\u043e\u0432\u0441\u0435\u043c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e VWO. \u042d\u0442\u043e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430, [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-35259","post","type-post","status-publish","format-standard","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412 \u0434\u0435\u043a\u0430\u0431\u0440\u0435 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u043e\u0442\u0447\u0435\u0442 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u043e\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 VWO. \u0412\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0442\u0447\u0435\u0442\u043e\u0432 \u0434\u043b\u044f \u043a\u0440\u0443\u043f\u043d\u043e\u0433\u043e \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u043f\u043e\u043c\u0435\u0440\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u043c.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u0434\u043d\u043e\u0433\u043e SQL \u0440\u0430\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412 \u0434\u0435\u043a\u0430\u0431\u0440\u0435 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u043e\u0442\u0447\u0435\u0442 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u043e\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 VWO. \u0412\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0442\u0447\u0435\u0442\u043e\u0432 \u0434\u043b\u044f \u043a\u0440\u0443\u043f\u043d\u043e\u0433\u043e \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u043f\u043e\u043c\u0435\u0440\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u043c.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:03:17+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:03:17+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Historia jednego \u015bledztwa SQL | ProHoster","description":"W grudniu ubieg\u0142ego roku otrzyma\u0142em interesuj\u0105cy raport o b\u0142\u0119dzie od zespo\u0142u wsparcia VWO. Czas \u0142adowania jednego z raport\u00f3w analitycznych dla du\u017cego klienta korporacyjnego wydawa\u0142 si\u0119 niebotycznie d\u0142ugi.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u0434\u043d\u043e\u0433\u043e SQL \u0440\u0430\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f | ProHoster","og:description":"\u0412 \u0434\u0435\u043a\u0430\u0431\u0440\u0435 \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e \u0433\u043e\u0434\u0430 \u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u043e\u0442\u0447\u0435\u0442 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u043e\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 VWO. \u0412\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043e\u0442\u0447\u0435\u0442\u043e\u0432 \u0434\u043b\u044f \u043a\u0440\u0443\u043f\u043d\u043e\u0433\u043e \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u043f\u043e\u043c\u0435\u0440\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u043c.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:03:17+00:00","article:modified_time":"2019-10-31T19:03:17+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"35259","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 22:33:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 02:07:28","updated":"2026-01-21 22:33:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/35259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=35259"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/35259\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=35259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=35259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=35259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}