{"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\/de\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","title":{"rendered":"Die Geschichte einer SQL-Untersuchung","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Im Dezember letzten Jahres erhielt ich einen interessanten Fehlerbericht vom Support-Team von VWO. Die Ladezeit eines der Analyseberichte f\u00fcr einen gro\u00dfen Unternehmenskunden erschien \u00fcberm\u00e4\u00dfig lang. Da dies in meinem Verantwortungsbereich liegt, konzentrierte ich mich sofort darauf, das Problem zu l\u00f6sen.<\/p>\n<p><\/p>\n<h2>Hintergrund<\/h2>\n<p><\/p>\n<p>Um zu verdeutlichen, worum es geht, m\u00f6chte ich kurz etwas \u00fcber VWO erz\u00e4hlen. Dies ist eine Plattform, mit der man verschiedene zielgerichtete Kampagnen auf seinen Websites durchf\u00fchren kann: A\/B-Tests, Besucher- und Konversionsverfolgung, Analyse des Verkaufstrichters, Anzeige von Heatmaps und Wiedergabe von Besuchsaufzeichnungen.<\/p>\n<p><\/p>\n<p>Das Wichtigste an der Plattform ist jedoch die Erstellung von Berichten. Alle genannten Funktionen h\u00e4ngen miteinander zusammen. F\u00fcr Unternehmenskunden w\u00e4re eine gro\u00dfe Menge an Informationen ohne eine leistungsstarke Plattform, die diese f\u00fcr die Analyse darstellt, einfach nutzlos.<\/p>\n<p><\/p>\n<p>Mit der Plattform kann man beliebige Anfragen auf gro\u00dfen Datens\u00e4tzen stellen. Hier ist ein einfaches Beispiel:<\/p>\n<p><\/p>\n<pre>Zeigen Sie alle Klicks auf der Seite \"abc.com\"\nVon &lt;Datum d1&gt; BIS &lt;Datum d2&gt;\nf\u00fcr Personen, die\nChrome verwendet haben ODER\n(in Europa waren UND iPhone verwendet haben)<\/pre>\n<p><\/p>\n<p>Beachten Sie die booleschen Operatoren. Diese stehen Kunden in der Abfrageoberfl\u00e4che zur Verf\u00fcgung, um beliebig komplexe Anfragen f\u00fcr Abfragen zu erstellen.<\/p>\n<p><\/p>\n<h2>Langsame Anfrage<\/h2>\n<p><\/p>\n<p>Der betreffende Kunde hat versucht, etwas zu tun, das intuitiv schnell funktionieren sollte:<\/p>\n<p><\/p>\n<pre>Zeige alle Sitzungsaufzeichnungen\nf\u00fcr Benutzer, die eine beliebige Seite\nmit einer URL besucht haben, die \"\/jobs\" enth\u00e4lt<\/pre>\n<p><\/p>\n<p>Auf dieser Website gab es eine enorme Menge an Verkehr, und wir haben \u00fcber eine Million einzigartiger URLs nur daf\u00fcr gespeichert. Und sie wollten ein recht einfaches URL-Muster finden, das zu ihrem Gesch\u00e4ftsmodell passt.<\/p>\n<p>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Vorl\u00e4ufige Untersuchung<\/h2>\n<p><\/p>\n<p>Lassen Sie uns sehen, was in der Datenbank passiert. Hier ist die urspr\u00fcngliche langsame SQL-Anfrage:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">W\u00c4HLEN \n    COUNT(*) \nVON \n    acc_{account_id}.urls AS recordings_urls, \n    acc_{account_id}.recording_data AS recording_data, \n    acc_{account_id}.sessions AS sessions \nWO \n    recording_data.usp_id = sessions.usp_id \n    UND sessions.referrer_id = recordings_urls.id \n    UND  ( urls &amp;&amp; array(select id from acc_{account_id}.urls where url ILIKE '%enterprise_customer.com\/jobs%')::text[] ) \n    UND r_time &gt; to_timestamp(1542585600) \n    UND r_time = 5 \n    UND recording_data.num_of_pages &gt; 0 ;<\/code><\/pre>\n<p><\/p>\n<p>Hier sind die Zeitangaben:<\/p>\n<p><\/p>\n<pre>Geplante Zeit: 1,480 ms\nAusf\u00fchrungszeit: 1.431.924,650 ms<\/pre>\n<p><\/p>\n<p>Die Abfrage durchlief 150.000 Zeilen. Der Abfrage-Planer zeigte einige interessante Details, jedoch keine offensichtlichen Engp\u00e4sse.<\/p>\n<p><\/p>\n<p>Lassen Sie uns die Abfrage weiter untersuchen. Wie zu sehen ist, f\u00fchrt sie <code>JOIN<\/code> drei Tabellen aus:<\/p>\n<p><\/p>\n<ol>\n<li><strong>sessions<\/strong>: um Sitzungsinformationen anzuzeigen: Browser, User-Agent, Land usw.<\/li>\n<li><strong>recording_data<\/strong>: aufgezeichnete URLs, Seiten, Besuchsdauer<\/li>\n<li><strong>urls<\/strong>: um die Duplizierung extrem gro\u00dfer URLs zu vermeiden, speichern wir sie in einer separaten Tabelle.<\/li>\n<\/ol>\n<p><\/p>\n<p>Bitte beachten Sie auch, dass alle unsere Tabellen bereits nach <code>account_id<\/code>unterteilt sind. So wird die Situation ausgeschlossen, dass durch ein besonders gro\u00dfes Konto Probleme bei anderen entstehen.<\/p>\n<p><\/p>\n<h2>Auf der Suche nach Hinweisen<\/h2>\n<p><\/p>\n<p>Bei genauerer Betrachtung sehen wir, dass etwas mit der spezifischen Anfrage nicht stimmt. Es lohnt sich, diese Zeile anzusehen:<\/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>Der erste Gedanke war, dass m\u00f6glicherweise wegen <code>ILIKE<\/code> bei all diesen langen URLs (wir haben \u00fcber 1,4 Millionen <strong>einzigartige\u00a0<\/strong>URLs, die f\u00fcr dieses Konto gesammelt wurden) die Leistung beeintr\u00e4chtigt sein k\u00f6nnte.<\/p>\n<p><\/p>\n<p>Aber nein \u2013 es liegt nicht daran!<\/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 rows)\n\nZeit: 5231.765 ms<\/code><\/pre>\n<p><\/p>\n<p>Die tats\u00e4chliche Suchanfrage nach dem Muster dauert nur 5 Sekunden. Die Mustererkennung bei einer Million einzigartiger URLs scheint eindeutig kein Problem darzustellen.<\/p>\n<p><\/p>\n<p>Der n\u00e4chste Verd\u00e4chtige auf der Liste sind einige <code>JOIN<\/code>. M\u00f6glicherweise hat deren \u00fcberm\u00e4\u00dfige Nutzung zu einer Verlangsamung gef\u00fchrt? Normalerweise <code>JOIN<\/code>&#8216;\u044b \u2014 \u0441\u0430\u043c\u044b\u0435 \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0435 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u044b \u043d\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e, \u043d\u043e \u044f \u043d\u0435 \u0432\u0435\u0440\u0438\u043b, \u0447\u0442\u043e \u043d\u0430\u0448 \u0441\u043b\u0443\u0447\u0430\u0439 \u0442\u0438\u043f\u043e\u0432\u043e\u0439.<\/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 row)\n\nZeit: 147.851 ms<\/code><\/pre>\n<p><\/p>\n<p>Und das war auch nicht unser Fall. <code>JOIN<\/code>&#8216;\u044b \u043e\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u0432\u0435\u0441\u044c\u043c\u0430 \u0431\u044b\u0441\u0442\u0440\u044b\u043c\u0438.<\/p>\n<p><\/p>\n<h2>Wir schr\u00e4nken den Kreis der Verd\u00e4chtigen ein.<\/h2>\n<p><\/p>\n<p>Ich war bereit, die Anfrage zu ver\u00e4ndern, um m\u00f6gliche Leistungsverbesserungen zu erreichen. Mein Team und ich haben zwei Hauptideen entwickelt:<\/p>\n<p><\/p>\n<ul>\n<li><strong>EXISTS f\u00fcr die URL-Subabfrage verwenden.<\/strong>: Wir wollten erneut \u00fcberpr\u00fcfen, ob es Probleme mit der Subabfrage f\u00fcr die URLs gibt. Eine M\u00f6glichkeit, dies zu erreichen, ist einfach <code>EXISTS<\/code>. <code>EXISTS<\/code> <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS\">herausfinden, ob der Benutzer Animationseffekte deaktiviert hat und auch verschiedene Animationsm\u00f6glichkeiten auf der Website deaktivieren, wie beispielsweise den Wackeleffekt bei den Schaltfl\u00e4chen, die zur Aufmerksamkeit geworben werden;<\/a><\/noindex> die Leistung erheblich zu verbessern, da die Abfrage sofort stoppt, sobald sie eine einzige Zeile anhand der Bedingung findet.<\/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>Ja, genau. Eine Subabfrage, die in\u00a0<code>EXISTS<\/code>, macht alles super schnell. Die n\u00e4chste logische Frage ist, warum die Anfragen mit <code>JOIN<\/code>-en und die Subabfrage f\u00fcr sich genommen schnell sind, aber zusammen schrecklich langsam werden?<\/p>\n<p><\/p>\n<ul>\n<li><strong>Wir verschieben die Subabfrage in eine CTE. <\/strong>: Wenn die Anfrage selbst schnell ist, k\u00f6nnen wir einfach zuerst das schnelle Ergebnis berechnen und es dann der Hauptanfrage zur Verf\u00fcgung stellen.<\/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>Aber das war immer noch sehr langsam.<\/p>\n<p><\/p>\n<h2>Den Schuldigen finden<\/h2>\n<p><\/p>\n<p>W\u00e4hrend all dieser Zeit schwebte ein kleines Detail vor meinen Augen, von dem ich st\u00e4ndig abgelenkt war. Aber da ich nichts anderes mehr hatte, beschloss ich, es mir anzusehen. Ich spreche von <code>&amp;&amp;<\/code> Operator. W\u00e4hrend <code>EXISTS<\/code> einfach die Leistung verbessert wurde, <code>&amp;&amp;<\/code> war der einzige verbleibende gemeinsame Faktor in allen Versionen der langsamen Anfrage.<\/p>\n<p><\/p>\n<p>Wenn wir auf <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/9.1\/functions-array.html\">Dokumentation<\/a><\/noindex>, sehen wir, dass <code>&amp;&amp;<\/code> verwendet wird, wenn es darum geht, gemeinsame Elemente zwischen zwei Arrays zu finden.<\/p>\n<p><\/p>\n<p>Im urspr\u00fcnglichen Antrag ist das:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">UND  (  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>Das bedeutet, dass wir eine Mustersuche in unseren URLs durchf\u00fchren, um dann eine Schnittmenge mit allen URLs mit gemeinsamen Eintr\u00e4gen zu finden. Das ist etwas verwirrend, da \u201eurls\u201c hier nicht auf eine Tabelle verweist, die alle URL-Adressen enth\u00e4lt, sondern auf die Spalte \u201eurls\u201c in der Tabelle. <code>recording_data<\/code>.<\/p>\n<p><\/p>\n<p>Mit dem Anstieg der Verdachtsmomente gegen <code>&amp;&amp;<\/code>, versuchte ich, Best\u00e4tigungen im generierten Abfrageplan zu finden, <code>EXPLAIN ANALYZE<\/code> (ich hatte bereits einen gespeicherten Plan, aber es ist mir in der Regel lieber, in SQL zu experimentieren, als die Intransparenzen der Abfrageplaner zu verstehen).<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Filter: ((urls &amp;&amp; ($0)::text[]) AND (r_time &gt; '2018-12-17 12:17:23+00'::timestamp with time zone) AND (r_time = '5'::double precision) AND (num_of_pages &gt; 0))\n                           Vom Filter entfernte Zeilen: 52710<\/code><\/pre>\n<p><\/p>\n<p>Es gab mehrere Filterzeilen nur aus <code>&amp;&amp;<\/code>. Das bedeutete, dass diese Operation nicht nur kostspielig war, sondern auch mehrfach ausgef\u00fchrt wurde.<\/p>\n<p><\/p>\n<p>Ich \u00fcberpr\u00fcfte dies, indem ich die Bedingung isolierte<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT 1\nFROM \n    acc_{account_id}.urls as recordings_urls, \n    acc_{account_id}.recording_data_30 as recording_data_30, \n    acc_{account_id}.sessions_30 as sessions_30 \nWHERE \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>Diese Abfrage wurde langsam ausgef\u00fchrt. Da <code>JOIN<\/code>-s schnell sind und Unterabfragen schnell sind, blieb nur der <code>&amp;&amp;<\/code> Operator \u00fcbrig.<\/p>\n<p><\/p>\n<p>Dies ist der entscheidende Schritt. Wir m\u00fcssen stets in der gesamten Haupttabelle der URLs nach Mustern suchen und immer die Schnittmengen finden. Eine direkte Suche in den URL-Eintr\u00e4gen ist nicht m\u00f6glich, da es sich lediglich um IDs handelt, die auf <code>urls<\/code>.<\/p>\n<p><\/p>\n<h2>den Weg zur L\u00f6sung<\/h2>\n<p><\/p>\n<p><code>&amp;&amp;<\/code> langsam ist, weil beide Mengen enorm sind. Der Vorgang wird relativ schnell sein, wenn ich <code>urls<\/code> findet man <code>{ \"http:\/\/google.com\/\", \"http:\/\/wingify.com\/\" }<\/code>.<\/p>\n<p><\/p>\n<p>begonnen habe, nach einer M\u00f6glichkeit zu suchen, in Postgres Schnittmengen zu erstellen, ohne <code>&amp;&amp;<\/code>, jedoch ohne nennenswerten Erfolg.<\/p>\n<p><\/p>\n<p>Schlie\u00dflich haben wir beschlossen, das Problem isoliert zu l\u00f6sen: Gib mir alle <code>urls<\/code> \u0441\u0442\u0440\u043e\u043a\u0438, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0440\u043b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0448\u0430\u0431\u043b\u043e\u043d\u0443. \u0411\u0435\u0437 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u043b\u043e\u0432\u0438\u0439 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 &#8212;\u00a0<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECT urls.url\nFROM \n\tacc_{account_id}.urls AS urls,\n\t(SELECT unnest(recording_data.urls) AS id) AS unrolled_urls\nWHERE\n\turls.id = unrolled_urls.id AND\n\turls.url  ILIKE  '%jobs%'<\/code><\/pre>\n<p><\/p>\n<p>Anstelle von\u00a0<code>JOIN<\/code> Bei der Syntax habe ich einfach eine Teilabfrage verwendet und die <code>recording_data.urls<\/code> Array entrollt, um die Bedingung direkt anwenden zu k\u00f6nnen in <code>WHERE<\/code>.<\/p>\n<p><\/p>\n<p>Das Wichtigste hier ist, dass <code>&amp;&amp;<\/code> wird verwendet, um zu \u00fcberpr\u00fcfen, ob dieser Datensatz die entsprechende URL enth\u00e4lt. Wenn man genau hinsieht, kann man in dieser Operation das Durchlaufen von Array-Elementen (oder Tabellenzeilen) und das Stoppen bei erf\u00fcllten Bedingungen erkennen. Kommt Ihnen das bekannt vor? Ah, <code>EXISTS<\/code>.<\/p>\n<p><\/p>\n<p>Da bei <code>recording_data.urls<\/code> von au\u00dferhalb des Kontextes der Unterabfrage verwiesen werden kann, k\u00f6nnen wir zu unserem alten Freund zur\u00fcckkehren <code>EXISTS<\/code> und ihn um die Unterabfrage wickeln.<\/p>\n<p><\/p>\n<p>Fassen wir alles zusammen, erhalten wir die endg\u00fcltige optimierte Abfrage:<\/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  (  1 = 1  )  \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    AND EXISTS(\n        SELECT urls.url\n        FROM \n            acc_{account_id}.urls as urls,\n            (SELECT unnest(urls) AS rec_url_id FROM acc_{account_id}.recording_data) \n            AS unrolled_urls\n        WHERE\n            urls.id = unrolled_urls.rec_url_id AND\n            urls.url ILIKE '%enterprise_customer.com\/jobs%'\n    );\n<\/code><\/pre>\n<p><\/p>\n<p>Und die endg\u00fcltige Ausf\u00fchrungszeit <code>Zeit: 1898,717 ms<\/code> Zeit zu feiern?!?<\/p>\n<p><\/p>\n<p>Nicht so schnell! Zuerst muss die Richtigkeit \u00fcberpr\u00fcft werden. Ich war \u00e4u\u00dferst misstrauisch gegen\u00fcber <code>EXISTS<\/code> Optimierung, da sie die Logik auf ein fr\u00fcheres Ende \u00e4ndert. Wir m\u00fcssen sicherstellen, dass wir keinen nicht offensichtlichen Fehler in die Anfrage eingef\u00fcgt haben.<\/p>\n<p><\/p>\n<p>Eine einfache \u00dcberpr\u00fcfung bestand darin, <code>count(*)<\/code> und sowohl bei langsamen als auch bei schnellen Anfragen f\u00fcr eine Vielzahl von verschiedenen Datens\u00e4tzen. Anschlie\u00dfend habe ich f\u00fcr eine kleine Teilmenge der Daten die Richtigkeit aller Ergebnisse manuell \u00fcberpr\u00fcft.<\/p>\n<p><\/p>\n<p>Alle \u00dcberpr\u00fcfungen ergaben durchweg positive Ergebnisse. Wir haben alles behoben!<\/p>\n<p><\/p>\n<h2>Learnings<\/h2>\n<p><\/p>\n<p>Aus dieser Geschichte lassen sich einige Lehren ziehen:<\/p>\n<p><\/p>\n<ol>\n<li>Abfragepl\u00e4ne erz\u00e4hlen nicht die ganze Geschichte, k\u00f6nnen aber Hinweise geben.<\/li>\n<li>Die Hauptverd\u00e4chtigen sind nicht immer die tats\u00e4chlichen Schuldigen.<\/li>\n<li>Langsame Anfragen k\u00f6nnen aufgedeckt werden, um Engp\u00e4sse zu isolieren.<\/li>\n<li>Nicht alle Optimierungen sind von Natur aus reduktiv.<\/li>\n<li>Nutzung <code>EXIST<\/code>, wo m\u00f6glich, kann zu einem erheblichen Leistungszuwachs f\u00fchren.<\/li>\n<\/ol>\n<p><\/p>\n<h2>Fazit<\/h2>\n<p><\/p>\n<p>Wir haben die Abfragezeit von etwa 24 Minuten auf 2 Sekunden reduziert \u2014 das ist ein erheblicher Leistungszuwachs! Auch wenn dieser Artikel lang war, fanden alle Experimente an einem Tag statt und brauchen sch\u00e4tzungsweise 1,5 bis 2 Stunden f\u00fcr Optimierungen und Tests.<\/p>\n<p><\/p>\n<p>SQL ist eine wunderbare Sprache, wenn man sich nicht scheut, sie zu lernen und zu nutzen. Mit einem soliden Verst\u00e4ndnis daf\u00fcr, wie SQL-Abfragen ausgef\u00fchrt werden, wie Datenbanken Abfragepl\u00e4ne erstellen, wie Indizes funktionieren und welche Datenmengen man verarbeitet, k\u00f6nnen Sie Ihre Abfragen erheblich optimieren. Es ist jedoch ebenso wichtig, weiterhin verschiedene Ans\u00e4tze auszuprobieren und das Problem schrittweise zu zerlegen, um Engp\u00e4sse zu finden.<\/p>\n<p><\/p>\n<p>Der beste Teil beim Erreichen solcher Ergebnisse ist die sp\u00fcrbare Verbesserung der Geschwindigkeit \u2014 wenn ein Bericht, der zuvor nicht einmal geladen wurde, jetzt fast sofort geladen wird.<\/p>\n<p><\/p>\n<p><strong>Besonderer Dank\u00a0<\/strong>meinen Kollegen\u00a0<em>im Team: Aditie Mishra<\/em>,\u00a0<em>Aditie Gaur\u00a0<\/em>und\u00a0<em><noindex><a rel=\"nofollow\" href=\"https:\/\/twitter.com\/s0ftvar\">Varun Malhotra\u00a0<\/a><\/noindex><\/em>f\u00fcr das Brainstorming und\u00a0<em>Dinku Pandir\u00a0<\/em>daf\u00fcr, dass er einen wichtigen Fehler in unserer finalen Abfrage gefunden hat, bevor wir uns endg\u00fcltig von ihr verabschiedet haben!<\/p>\n<p>Quelle: <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.0.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. \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,\" \/>\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\/de\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.0.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\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. \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,\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/de\/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\udd47Die Geschichte einer SQL-Untersuchung | ProHoster","description":"Im Dezember letzten Jahres erhielt ich einen interessanten Fehlerbericht vom Support-Team von VWO. Die Ladezeit eines der Analyseberichte f\u00fcr einen gro\u00dfen Unternehmenskunden schien unangemessen lang. Da dies in meinem Verantwortungsbereich liegt, konzentrierte ich mich sofort darauf, das Problem zu l\u00f6sen. Hintergrund Um zu verstehen, worum es geht, m\u00f6chte ich kurz etwas \u00fcber VWO erz\u00e4hlen. Es ist eine Plattform,","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","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. \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,","og:url":"https:\/\/prohoster.info\/de\/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\/de\/wp-json\/wp\/v2\/posts\/35259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=35259"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/35259\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=35259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=35259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=35259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}