{"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-Ermittlung","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Im Dezember letzten Jahres erhielt ich einen interessanten Fehlerbericht vom VWO-Supportteam. Die Ladezeit eines der Analytics-Berichte f\u00fcr einen gro\u00dfen Unternehmenskunden schien \u00fcberm\u00e4\u00dfig lang zu sein. Da dies in meinem Verantwortungsbereich liegt, konzentrierte ich mich sofort auf die L\u00f6sung des Problems.<\/p>\n<p><\/p>\n<h2>Vorgeschichte<\/h2>\n<p><\/p>\n<p>Um zu erkl\u00e4ren, worum es geht, m\u00f6chte ich ein wenig \u00fcber VWO erz\u00e4hlen. Es ist eine Plattform, die es erm\u00f6glicht, verschiedene zielgerichtete Kampagnen auf Ihren Websites zu starten: A\/B-Tests durchzuf\u00fchren, Besucher und Conversions zu verfolgen, die Verkaufstrichteranalyse zu erstellen, Heatmaps anzuzeigen und Besuchsaufzeichnungen abzuspielen.<\/p>\n<p><\/p>\n<p>Das Wichtigste an der Plattform ist jedoch die Erstellung von Berichten. Alle oben genannten Funktionen sind miteinander verkn\u00fcpft. Und f\u00fcr Unternehmenskunden w\u00e4re ein riesiger Datenpool ohne eine leistungsstarke Plattform, die diese Daten f\u00fcr die Analyse darstellt, einfach nutzlos.<\/p>\n<p><\/p>\n<p>Mit der Plattform k\u00f6nnen Sie beliebige Abfragen auf gro\u00dfen Datens\u00e4tzen durchf\u00fchren. Hier ist ein einfaches Beispiel:<\/p>\n<p><\/p>\n<pre>Zeige alle Klicks auf der Seite \"abc.com\" \nVON &lt;Datum d1&gt; BIS &lt;Datum d2&gt; \nf\u00fcr Personen, die \nChrome benutzt haben ODER \n(in Europa waren UND iPhone benutzt haben)<\/pre>\n<p><\/p>\n<p>Beachten Sie die booleschen Operatoren. Diese sind im Abfrageinterface f\u00fcr Kunden verf\u00fcgbar, um beliebig komplexe Abfragen zur Datenextraktion zu erstellen.<\/p>\n<p><\/p>\n<h2>Langsame Abfrage<\/h2>\n<p><\/p>\n<p>Der betreffende Kunde versuchte, etwas zu tun, das intuitiv schnell funktionieren sollte:<\/p>\n<p><\/p>\n<pre>Zeige alle Sitzungsaufzeichnungen \nf\u00fcr Nutzer, die eine beliebige Seite mit einer URL besucht haben, die \"\\\/jobs\" enth\u00e4lt<\/pre>\n<p><\/p>\n<p>Auf dieser Website war ein enormes Verkehrsaufkommen, und wir speicherten mehr als eine Million einzigartiger URLs nur f\u00fcr sie. Sie wollten ein ziemlich einfaches URL-Muster finden, das zu ihrem Gesch\u00e4ftsmodell geh\u00f6rte.<\/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-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 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 &lt; to_timestamp(1545177599) \n    AND recording_data.duration &gt;=5 \n    AND 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: 1431924.650 ms<\/pre>\n<p><\/p>\n<p>Die Anfrage umfasste 150.000 Zeilen. Der Abfrageplaner zeigte ein paar interessante Details, aber keine offensichtlichen Engp\u00e4sse.<\/p>\n<p><\/p>\n<p>Lassen Sie uns die Anfrage weiter untersuchen. Wie zu sehen ist, macht sie <code>JOIN<\/code> drei Tabellen:<\/p>\n<p><\/p>\n<ol>\n<li><strong>sessions<\/strong>: zur Anzeige von Sitzungsinformationen: Browser, Benutzer-Agent, Land und so weiter.<\/li>\n<li><strong>recording_data<\/strong>: aufgezeichnete URLs, Seiten, Dauer der Besuche<\/li>\n<li><strong>urls<\/strong>: um die doppelte Speicherung extrem langer 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>getrennt sind. So wird ausgeschlossen, dass aufgrund eines besonders gro\u00dfen Kontos Probleme bei anderen auftreten.<\/p>\n<p><\/p>\n<h2>Auf der Suche nach Hinweisen<\/h2>\n<p><\/p>\n<p>Bei n\u00e4herer Betrachtung sehen wir, dass etwas an dieser speziellen Anfrage nicht stimmt. Es ist notwendig, sich diese Zeile genauer 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>einzigartiger\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 - das ist nicht der Fall!<\/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 Abfrage nach dem Muster dauert nur 5 Sekunden. Die Musterabfrage bei einer Million einzigartiger URLs ist offensichtlich kein Problem.<\/p>\n<p><\/p>\n<p>Der n\u00e4chste Verd\u00e4chtige auf der Liste sind einige <code>JOIN<\/code>. M\u00f6glicherweise hat ihr \u00fcberm\u00e4\u00dfiger Gebrauch zu einer Verlangsamung gef\u00fchrt? In der Regel <code>JOIN<\/code>\u201aE\u2018 sind die offensichtlichsten Kandidaten f\u00fcr Leistungsprobleme, aber ich glaubte nicht, dass unser Fall typisch war.<\/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 ebenfalls nicht unser Fall. <code>JOIN<\/code>\u201aE\u2018 erwiesen sich als ziemlich schnell.<\/p>\n<p><\/p>\n<h2>Wir verengen den Kreis der Verd\u00e4chtigen<\/h2>\n<p><\/p>\n<p>Ich war bereit, die Abfrage zu \u00e4ndern, um m\u00f6gliche Leistungsverbesserungen zu erreichen. Mein Team und ich entwickelten 2 Hauptideen:<\/p>\n<p><\/p>\n<ul>\n<li><strong>EXISTS f\u00fcr die URL-Unterabfrage verwenden<\/strong>: Wir wollten noch einmal \u00fcberpr\u00fcfen, ob es Probleme mit der Unterabfrage f\u00fcr die URLs gibt. Eine M\u00f6glichkeit, dies zu erreichen, besteht darin, einfach zu verwenden <code>EXISTS<\/code>. <code>EXISTS<\/code> <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS\">pl\u00f6tzliche Fehler verarbeiten und n\u00fctzliche Details zur Fehlersuche anzeigen, wie z. B. einen Stack-Trace. Die Fehlermeldung wurde vereinfacht \u2013 es reicht aus, auf den Link zu klicken.<\/a><\/noindex> Die Leistung erheblich verbessern, da sie sofort endet, sobald die einzige Zeile mit der Bedingung gefunden wird.<\/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 Zeile)\nZeit: 1636.637 ms<\/code><\/pre>\n<p><\/p>\n<p>Ja. Eine Unterabfrage, die in\u00a0<code>EXISTS<\/code>, macht alles super schnell. Die n\u00e4chste logische Frage ist, warum die Abfrage mit <code>JOIN<\/code>-en und die Unterabfrage f\u00fcr sich genommen schnell sind, aber zusammen schrecklich langsam sind?<\/p>\n<p><\/p>\n<ul>\n<li><strong>Wir verschieben die Unterabfrage in CTE <\/strong>: wenn die Abfrage schnell f\u00fcr sich ist, k\u00f6nnen wir einfach zuerst das schnelle Ergebnis berechnen und es dann der Hauptabfrage bereitstellen.<\/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>Wir finden den \u00dcbelt\u00e4ter.<\/h2>\n<p><\/p>\n<p>W\u00e4hrend all dieser Zeit hat ein kleines Detail immer wieder in meinem Kopf geschwebt, von dem ich st\u00e4ndig abgelenkt wurde. Aber da nichts mehr \u00fcbrig war, beschloss ich, es mir auch anzusehen. Ich spreche von <code>&amp;&amp;<\/code> Operator. W\u00e4hrend <code>EXISTS<\/code> ich einfach die Leistung verbessert habe, <code>&amp;&amp;<\/code> war der einzige verbleibende gemeinsame Faktor in allen Versionen der langsamen Abfrage.<\/p>\n<p><\/p>\n<p>Wenn wir uns <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/9.1\/functions-array.html\">Dokumentation<\/a><\/noindex>ansehen, 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>In der Originalabfrage ist es:<\/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>Was bedeutet, dass wir eine Musterabgleichung auf unsere URLs durchf\u00fchren, dann das Schnittmuster mit allen URLs in den gemeinsamen Aufzeichnungen finden. Das ist ein bisschen verwirrend, da \"urls\" hier nicht auf die Tabelle verweist, die alle URLs enth\u00e4lt, sondern auf die Spalte \"urls\" in der Tabelle. <code>recording_data<\/code>.<\/p>\n<p><\/p>\n<p>Mit wachsendem Verdacht in Bezug auf <code>&amp;&amp;<\/code>, habe ich versucht, diese im generierten Abfrageplan zu best\u00e4tigen. <code>EXPLAIN ANALYZE<\/code> (Ich hatte bereits einen gespeicherten Plan, aber es ist mir normalerweise lieber, in SQL zu experimentieren, als die Intransparenzen von Abfrageplanern zu verstehen).<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Filter: ((urls &amp;&amp; ($0)::text[]) UND (r_time &gt; '2018-12-17 12:17:23+00'::timestamp with time zone) UND (r_time = '5'::double precision) UND (num_of_pages &gt; 0))\n                           Vom Filter entfernte Zeilen: 52710<\/code><\/pre>\n<p><\/p>\n<p>Es gab einige Filterzeilen nur aus <code>&amp;&amp;<\/code>. Das bedeutete, dass diese Operation nicht nur teuer war, sondern auch mehrmals ausgef\u00fchrt wurde.<\/p>\n<p><\/p>\n<p>Ich habe das \u00fcberpr\u00fcft, indem ich die Bedingung isoliert habe<\/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>Das ist nur der kritische Vorgang. Wir m\u00fcssen immer in der gesamten Haupttabelle der URLs suchen, um im Muster zu suchen, und wir m\u00fcssen immer die \u00dcberschneidungen finden. Wir k\u00f6nnen nicht direkt in den URL-Eintr\u00e4gen suchen, da dies nur IDs sind, die auf <code>urls<\/code>.<\/p>\n<p><\/p>\n<h2>Auf dem Weg zur L\u00f6sung<\/h2>\n<p><\/p>\n<p><code>&amp;&amp;<\/code> langsam ist, da beide Mengen gro\u00df sind. Der Vorgang wird relativ schnell sein, wenn ich <code>urls<\/code> auf <code>{ \"http:\/\/google.com\/\", \"http:\/\/wingify.com\/\" }<\/code>.<\/p>\n<p><\/p>\n<p>Ich begann nach einer M\u00f6glichkeit zu suchen, in Postgres Mengen zu schneiden, ohne zu <code>&amp;&amp;<\/code>, aber ohne besonderen Erfolg.<\/p>\n<p><\/p>\n<p>Schlie\u00dflich entschieden wir uns, das Problem isoliert zu l\u00f6sen: Gib mir alle <code>urls<\/code> Zeilen, f\u00fcr die die URL dem Muster entspricht. Ohne zus\u00e4tzliche Bedingungen wird dies \u2014\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 UND\n\turls.url ILIKE '%jobs%'<\/code><\/pre>\n<p><\/p>\n<p>Statt\u00a0<code>JOIN<\/code> syntaxisch habe ich einfach eine Unterabfrage verwendet und entrollte <code>recording_data.urls<\/code> das Array, sodass die Bedingung direkt in <code>WHERE<\/code>.<\/p>\n<p><\/p>\n<p>Das Wichtigste hier ist, dass <code>&amp;&amp;<\/code> verwendet wird, um zu \u00fcberpr\u00fcfen, ob dieser Eintrag die entsprechende URL enth\u00e4lt. Wenn man ein wenig blinzelt, kann man in diesem Vorgang das Durchlaufen der Array-Elemente (oder Tabellenzeilen) sehen und das Stoppen, wenn die Bedingung (\u00dcbereinstimmung) erf\u00fcllt ist. Kommt dir das bekannt vor? Ja, <code>EXISTS<\/code>.<\/p>\n<p><\/p>\n<p>Da auf <code>recording_data.urls<\/code> von au\u00dferhalb des Kontexts der Unterabfrage verwiesen werden kann, wenn dies passiert, k\u00f6nnen wir zu unserem alten Freund <code>EXISTS<\/code> zur\u00fcckkehren und ihn um die Unterabfrage wickeln.<\/p>\n<p><\/p>\n<p>Wenn wir alles zusammenfassen, erhalten wir die endg\u00fcltige optimierte Abfrage:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">W\u00c4HLEN Sie \n    count(*) \nVON \n    acc_{account_id}.urls als recordings_urls, \n    acc_{account_id}.recording_data als recording_data, \n    acc_{account_id}.sessions als sessions \nWO \n    recording_data.usp_id = sessions.usp_id \n    UND  (  1 = 1  )  \n    UND sessions.referrer_id = recordings_urls.id \n    UND r_time &gt; to_timestamp(1542585600) \n    UND r_time =5 \n    UND recording_data.num_of_pages &gt; 0\n    UND EXISTS(\n        W\u00c4HLEN urls.url\n        VON \n            acc_{account_id}.urls als urls,\n            (W\u00c4HLEN unnest(urls) ALS rec_url_id VON acc_{account_id}.recording_data) \n            ALS unrolled_urls\n        WO\n            urls.id = unrolled_urls.rec_url_id UND\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 zum Feiern?!?<\/p>\n<p><\/p>\n<p>Nicht so schnell! Zun\u00e4chst m\u00fcssen wir die Richtigkeit \u00fcberpr\u00fcfen. Ich war extrem misstrauisch gegen\u00fcber <code>EXISTS<\/code> der Optimierung, da sie die Logik f\u00fcr ein fr\u00fcheres Ende \u00e4ndert. Wir m\u00fcssen sicher sein, dass wir keinen offensichtlichen Fehler in der Anfrage hinzugef\u00fcgt haben.<\/p>\n<p><\/p>\n<p>Eine einfache \u00dcberpr\u00fcfung bestand darin, \ncount(*)\n auszuf\u00fchren \nund das sowohl f\u00fcr langsame als auch f\u00fcr schnelle Anfragen bei einer Vielzahl unterschiedlicher Datens\u00e4tze. Dann habe ich f\u00fcr eine kleine Teilmenge von Daten die Richtigkeit aller Ergebnisse manuell \u00fcberpr\u00fcft. <code>Alle \u00dcberpr\u00fcfungen ergaben konstant positive Ergebnisse. Wir haben alles repariert!<\/code> Abgeleitete Lektionen<\/p>\n<p><\/p>\n<p>Aus dieser Geschichte lassen sich einige Lektionen ziehen:<\/p>\n<p><\/p>\n<h2>Abfragepl\u00e4ne erz\u00e4hlen nicht die ganze Geschichte, k\u00f6nnen aber Hinweise geben<\/h2>\n<p><\/p>\n<p>Die Hauptverd\u00e4chtigen sind nicht immer die tats\u00e4chlichen Schuldigen<\/p>\n<p><\/p>\n<ol>\n<li>Langsame Anfragen k\u00f6nnen auseinandergezogen werden, um Engp\u00e4sse zu isolieren<\/li>\n<li>Nicht alle Optimierungen sind von Natur aus reduktiv<\/li>\n<li>EXIST<\/li>\n<li>, wo m\u00f6glich, kann zu einem drastischen Anstieg der Leistung f\u00fchren<\/li>\n<li>Verwendung <code>Wir haben die Abfragezeit von ~24 Minuten auf 2 Sekunden reduziert \u2013 ein erheblicher Leistungszuwachs! Obwohl dieser Artikel lang geworden ist, fanden alle Experimente, die wir durchgef\u00fchrt haben, an einem Tag statt und dauerten sch\u00e4tzungsweise 1,5 bis 2 Stunden f\u00fcr Optimierungen und Tests.<\/code>SQL ist eine wunderbare Sprache, wenn man keine Angst davor hat, sondern versucht, sie zu verstehen und zu nutzen. Mit einem guten Verst\u00e4ndnis daf\u00fcr, wie SQL-Abfragen ausgef\u00fchrt werden, wie die Datenbank Abfragepl\u00e4ne generiert, wie Indizes funktionieren und einfach der Gr\u00f6\u00dfe der Daten, mit denen man es zu tun hat, kann man im Hinblick auf das Optimieren von Abfragen sehr erfolgreich sein. Ebenso wichtig ist es jedoch, verschiedene Ans\u00e4tze weiter auszuprobieren und das Problem langsam zu zerlegen, um Engp\u00e4sse zu finden.<\/li>\n<\/ol>\n<p><\/p>\n<h2>Ausgabe<\/h2>\n<p><\/p>\n<p>Wir haben die Anfragezeit von etwa 24 Minuten auf 2 Sekunden reduziert \u2013 ein erheblicher Leistungsanstieg! Obwohl dieser Artikel lang ist, fanden alle Experimente, die wir durchgef\u00fchrt haben, an einem Tag statt und dauerten insgesamt 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 keine Angst davor hat, sondern versucht, sie zu verstehen und zu nutzen. Wenn Sie gut nachvollziehen k\u00f6nnen, wie SQL-Abfragen ausgef\u00fchrt werden, wie die Datenbank Abfragepl\u00e4ne generiert, wie Indizes funktionieren und einfach auch die Datenmenge, mit der Sie es zu tun haben, werden Sie bei der Optimierung von Abfragen sehr erfolgreich sein. Ebenso wichtig ist es jedoch, verschiedene Ans\u00e4tze auszuprobieren und das Problem langsam zu zerlegen, um Engp\u00e4sse zu finden.<\/p>\n<p><\/p>\n<p>Der beste Teil beim Erreichen solcher Ergebnisse ist die bemerkbare Verbesserung der Arbeitsgeschwindigkeit \u2013 als der Bericht, der zuvor nicht einmal geladen werden konnte, jetzt fast sofort geladen wird.<\/p>\n<p><\/p>\n<p><strong>Besonderer Dank\u00a0<\/strong>meinen Kollegen\u00a0<em>im Team von Aditiya Mishra<\/em>,\u00a0<em>Aditiya 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>Dinkar Pandir\u00a0<\/em>daf\u00fcr, dass er einen wichtigen Fehler in unserer finalen Anfrage gefunden hat, bevor wir uns endg\u00fcltig davon 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.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\/de\/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=\"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.\" \/>\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 VWO-Support-Team. Die Ladezeit eines der analytischen Berichte f\u00fcr einen gro\u00dfen Unternehmenskunden schien unverh\u00e4ltnism\u00e4\u00dfig lang zu sein.","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.","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}]}}