{"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\/sv\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","title":{"rendered":"Historien om en SQL-utredning","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>I december f\u00f6rra \u00e5ret fick jag en intressant felrapport fr\u00e5n VWO-supportteamet. Laddningstiden f\u00f6r en av de analytiska rapporterna f\u00f6r en stor f\u00f6retagsklient verkade orimligt l\u00e5ng. Eftersom det \u00e4r mitt ansvarsomr\u00e5de fokuserade jag genast p\u00e5 att l\u00f6sa problemet.<\/p>\n<p><\/p>\n<h2>Bakgrund<\/h2>\n<p><\/p>\n<p>F\u00f6r att klarg\u00f6ra vad det handlar om, ska jag kort s\u00e4ga n\u00e5gra ord om VWO. Det \u00e4r en plattform som g\u00f6r det m\u00f6jligt att k\u00f6ra olika riktade kampanjer p\u00e5 sina webbplatser: genomf\u00f6ra A\/B-experiment, sp\u00e5ra bes\u00f6kare och konverteringar, g\u00f6ra analys av f\u00f6rs\u00e4ljningstratten, visa v\u00e4rmekartor och spela upp bes\u00f6ksinspelningar.<\/p>\n<p><\/p>\n<p>Men det viktigaste med plattformen \u00e4r rapportgenerering. Alla ovanst\u00e5ende funktioner \u00e4r kopplade till varandra. F\u00f6r f\u00f6retag \u00e4r en stor m\u00e4ngd information praktiskt taget v\u00e4rdel\u00f6s utan en kraftfull plattform som presenterar den f\u00f6r analys.<\/p>\n<p><\/p>\n<p>Genom att anv\u00e4nda plattformen kan man g\u00f6ra skr\u00e4ddarsydda f\u00f6rfr\u00e5gningar p\u00e5 stora datam\u00e4ngder. H\u00e4r \u00e4r ett enkelt exempel:<\/p>\n<p><\/p>\n<pre>Visa alla klick p\u00e5 sidan \"abc.com\"\nFR\u00c5N &lt;datum d1&gt; TILL &lt;datum d2&gt;\nf\u00f6r personer som\nanv\u00e4nt Chrome ELLER\n(varit i Europa OCH anv\u00e4nt iPhone)<\/pre>\n<p><\/p>\n<p>Observera de booleska operatorerna. De \u00e4r tillg\u00e4ngliga f\u00f6r kunder i f\u00f6rfr\u00e5gningsgr\u00e4nssnittet f\u00f6r att g\u00f6ra hur komplexa f\u00f6rfr\u00e5gningar som helst f\u00f6r att f\u00e5 utvalda data.<\/p>\n<p><\/p>\n<h2>L\u00e5ngsam f\u00f6rfr\u00e5gan<\/h2>\n<p><\/p>\n<p>Klienten det handlar om f\u00f6rs\u00f6kte g\u00f6ra n\u00e5got som intuitivt borde g\u00e5 snabbt:<\/p>\n<p><\/p>\n<pre>Visa alla sessioner\nf\u00f6r anv\u00e4ndare som bes\u00f6kt vilken sida som helst\nmed en URL som inneh\u00e5ller \"\\\/jobs\"<\/pre>\n<p><\/p>\n<p>Denna webbplats hade en enorm m\u00e4ngd trafik och vi hade lagrat \u00f6ver en miljon unika URL-adresser bara f\u00f6r den. De ville hitta en ganska enkel mall av URL:er relaterad till deras aff\u00e4rsmodell.<\/p>\n<p>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Prelimin\u00e4r utredning<\/h2>\n<p><\/p>\n<p>L\u00e5t oss titta p\u00e5 vad som h\u00e4nder i databasen. H\u00e4r \u00e4r den ursprungliga l\u00e5ngsamma SQL-fr\u00e5gan:<\/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>H\u00e4r \u00e4r tiderna:<\/p>\n<p><\/p>\n<pre>Ber\u00e4knad tid: 1.480 ms\nExekveringstid: 1431924.650 ms<\/pre>\n<p><\/p>\n<p>Fr\u00e5gan gick igenom 150 000 rader. Fr\u00e5geschemal\u00e4ggaren visade n\u00e5gra intressanta detaljer, men inga uppenbara flaskhalsar.<\/p>\n<p><\/p>\n<p>L\u00e5t oss studera fr\u00e5gan vidare. Som vi kan se g\u00f6r den <code>JOIN<\/code> tre tabeller:<\/p>\n<p><\/p>\n<ol>\n<li><strong>sessions<\/strong>: f\u00f6r att visa sessionsinformation: webbl\u00e4sare, anv\u00e4ndaragenter, land och s\u00e5 vidare.<\/li>\n<li><strong>recording_data<\/strong>: inspelade URL:er, sidor, bes\u00f6kens varaktighet<\/li>\n<li><strong>urls<\/strong>: f\u00f6r att undvika duplicering av extremt stora URL:er, lagrar vi dem i en separat tabell.<\/li>\n<\/ol>\n<p><\/p>\n<p>Observera ocks\u00e5 att alla v\u00e5ra tabeller redan \u00e4r partitionerade efter <code>account_id<\/code>. P\u00e5 s\u00e5 s\u00e4tt utesluts situationen d\u00e4r ett s\u00e4rskilt stort konto orsakar problem f\u00f6r andra.<\/p>\n<p><\/p>\n<h2>I jakt p\u00e5 ledtr\u00e5dar<\/h2>\n<p><\/p>\n<p>Vid en n\u00e4rmare granskning ser vi att n\u00e5got \u00e4r fel med den specifika fr\u00e5gan. Det \u00e4r v\u00e4rt att titta p\u00e5 denna rad:<\/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>Den f\u00f6rsta tanken var att det kanske berodde p\u00e5 <code>ILIKE<\/code> p\u00e5 alla dessa l\u00e5nga URL:er (vi har mer \u00e4n 1,4 miljoner <strong>unika\u00a0<\/strong>URL-adresser som samlades in f\u00f6r detta konto) kanske prestandan kunde p\u00e5verkas.<\/p>\n<p><\/p>\n<p>Men, nej - det \u00e4r inte det!<\/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 rader)\n\nTid: 5231.765 ms<\/code><\/pre>\n<p><\/p>\n<p>Sj\u00e4lva s\u00f6kfr\u00e5gan tar bara 5 sekunder. S\u00f6kningen efter m\u00f6nster p\u00e5 en miljon unika URL:er \u00e4r uppenbarligen inte ett problem.<\/p>\n<p><\/p>\n<p>N\u00e4sta misst\u00e4nkte p\u00e5 listan - flera <code>JOIN<\/code>. Kanske deras \u00f6verdrivna anv\u00e4ndning ledde till en nedg\u00e5ng? Vanligtvis <code>JOIN<\/code>\u2019e \u00e4r de mest uppenbara kandidaterna f\u00f6r prestandaproblem, men jag trodde inte att v\u00e5rt fall var typiskt.<\/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 rad)\n\nTid: 147.851 ms<\/code><\/pre>\n<p><\/p>\n<p>Och det var heller inte v\u00e5rt fall. <code>JOIN<\/code>\u2019e visade sig vara ganska snabba.<\/p>\n<p><\/p>\n<h2>Vi smalnar av cirkeln av misst\u00e4nkta<\/h2>\n<p><\/p>\n<p>Jag var redo att b\u00f6rja \u00e4ndra fr\u00e5gan f\u00f6r att uppn\u00e5 eventuella prestandaf\u00f6rb\u00e4ttringar. Vi i teamet utvecklade 2 huvudid\u00e9er:<\/p>\n<p><\/p>\n<ul>\n<li><strong>Anv\u00e4nd EXISTS f\u00f6r URL-underrubriken<\/strong>: Vi ville dubbelkolla om det fanns n\u00e5gra problem med underrubriken f\u00f6r URL:er. Ett av s\u00e4tten att g\u00f6ra detta \u00e4r att helt enkelt anv\u00e4nda <code>EXISTS<\/code>. <code>EXISTS<\/code> <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS\">hantera pl\u00f6tsliga kraschar och visa anv\u00e4ndbara detaljer f\u00f6r fels\u00f6kning, som stacktrace. Att skicka felmeddelanden har f\u00f6renklats \u2014 det r\u00e4cker att klicka p\u00e5 l\u00e4nken.<\/a><\/noindex> betydligt f\u00f6rb\u00e4ttra prestandan eftersom det avslutas s\u00e5 snart det hittar den enda raden som uppfyller villkoret.<\/li>\n<\/ul>\n<p><\/p>\n<pre><code class=\"plaintext\">V\u00c4LJ\n\tcount(*) \nFR\u00c5N \n    acc_{account_id}.urls som recordings_urls,\n    acc_{account_id}.recording_data som recording_data,\n    acc_{account_id}.sessions som sessions\nD\u00c4R\n    recording_data.usp_id = sessions.usp_id\n    OCH  (  1 = 1  )\n    OCH sessions.referrer_id = recordings_urls.id\n    OCH  (existerar(select id fr\u00e5n acc_{account_id}.urls d\u00e4r url  ILIKE '%enterprise_customer.com\/jobs%'))\n    OCH r_time &gt; to_timestamp(1547585600)\n    OCH r_time =5\n    OCH recording_data.num_of_pages &gt; 0 ;\n count\n 32519\n(1 rad)\nTid: 1636.637 ms<\/code><\/pre>\n<p><\/p>\n<p>Ja. En underfr\u00e5ga, n\u00e4r den \u00e4r inslagen i\u00a0<code>EXISTS<\/code>, g\u00f6r allt supersnabbt. Den n\u00e4sta logiska fr\u00e5gan \u00e4r varf\u00f6r fr\u00e5gan med <code>JOIN<\/code>-er och underfr\u00e5gan i sig \u00e4r snabba var f\u00f6r sig, men extremt l\u00e5ngsamma tillsammans?<\/p>\n<p><\/p>\n<ul>\n<li><strong>Flyttar underfr\u00e5gan till CTE <\/strong>: om fr\u00e5gan \u00e4r snabb i sig sj\u00e4lv kan vi helt enkelt f\u00f6rst ber\u00e4kna det snabba resultatet och sedan \u00f6verl\u00e4mna det till huvudfr\u00e5gan.<\/li>\n<\/ul>\n<p><\/p>\n<pre><code class=\"plaintext\">MED matching_urls SOM (\n    v\u00e4lj id::text fr\u00e5n acc_{account_id}.urls d\u00e4r url  ILIKE  '%enterprise_customer.com\/jobs%'\n)\n\nV\u00c4LJ \n    count(*) FR\u00c5N acc_{account_id}.urls som recordings_urls, \n    acc_{account_id}.recording_data som recording_data, \n    acc_{account_id}.sessions som sessions,\n    matching_urls\nD\u00c4R \n    recording_data.usp_id = sessions.usp_id \n    OCH  (  1 = 1  )  \n    OCH sessions.referrer_id = recordings_urls.id\n    OCH (urls &amp;&amp; array(V\u00c4LJ id fr\u00e5n matching_urls)::text[])\n    OCH r_time &gt; to_timestamp(1542585600) \n    OCH r_time =5 \n    OCH recording_data.num_of_pages &gt; 0;<\/code><\/pre>\n<p><\/p>\n<p>Men det var fortfarande mycket l\u00e5ngsamt.<\/p>\n<p><\/p>\n<h2>Hittar syndaren<\/h2>\n<p><\/p>\n<p>Under hela tiden har en liten detalj f\u00f6rblivit i mitt bakhuvud, som jag st\u00e4ndigt bortf\u00f6rde. Men eftersom jag inte hade n\u00e5got annat kvar, best\u00e4mde jag mig f\u00f6r att \u00e4ven kolla den. Jag pratar om <code>&amp;&amp;<\/code> operat\u00f6ren. Medan <code>EXISTS<\/code> bara f\u00f6rb\u00e4ttrade prestandan, <code>&amp;&amp;<\/code> var den enda \u00e5terst\u00e5ende gemensamma faktorn i alla versioner av den l\u00e5ngsamma f\u00f6rfr\u00e5gan.<\/p>\n<p><\/p>\n<p>Tittar vi p\u00e5 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/9.1\/functions-array.html\">dokumentationen<\/a><\/noindex>, ser vi att <code>&amp;&amp;<\/code> anv\u00e4nds n\u00e4r man beh\u00f6ver hitta gemensamma element mellan tv\u00e5 arrayer.<\/p>\n<p><\/p>\n<p>I den ursprungliga fr\u00e5gan \u00e4r det:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">OCH  (  urls &amp;&amp;  array(v\u00e4lj id fr\u00e5n acc_{account_id}.urls d\u00e4r url  ILIKE  '%enterprise_customer.com\/jobs%')::text[]   )<\/code><\/pre>\n<p><\/p>\n<p>Vilket betyder att vi g\u00f6r en m\u00f6nsters\u00f6kning p\u00e5 v\u00e5ra URL:er, och sedan hittar vi sk\u00e4rningspunkten med alla URL:er som har gemensamma poster. Det \u00e4r lite f\u00f6rvirrande eftersom 'urls' h\u00e4r inte syftar p\u00e5 en tabell som inneh\u00e5ller alla URL:er, utan p\u00e5 kolumnen 'urls' i tabellen <code>recording_data<\/code>.<\/p>\n<p><\/p>\n<p>Med \u00f6kande misstankar om <code>&amp;&amp;<\/code>, f\u00f6rs\u00f6kte jag hitta bevis f\u00f6r dem i fr\u00e5geplanen som genererats av <code>EXPLAIN ANALYZE<\/code> (jag hade redan en sparad plan, men det \u00e4r oftast mer bekv\u00e4mt f\u00f6r mig att experimentera i SQL \u00e4n att f\u00f6rs\u00f6ka f\u00f6rst\u00e5 opaciteter hos fr\u00e5geplanerarna).<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Filter: ((urls &amp;&amp; ($0)::text[]) OCH (r_time &gt; '2018-12-17 12:17:23+00'::timestamp with time zone) OCH (r_time = '5'::double precision) OCH (num_of_pages &gt; 0))\n                           Rader borttagna av filter: 52710<\/code><\/pre>\n<p><\/p>\n<p>Det fanns n\u00e5gra rader filter bara fr\u00e5n <code>&amp;&amp;<\/code>. Vilket innebar att denna operation inte bara var kostsam, utan ocks\u00e5 utf\u00f6rdes flera g\u00e5nger.<\/p>\n<p><\/p>\n<p>Jag kontrollerade detta genom att isolera villkoret<\/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>Denna fr\u00e5ga k\u00f6rdes l\u00e5ngsamt. Eftersom <code>JOIN<\/code>-er \u00e4r snabba och subfr\u00e5gor \u00e4r snabba, \u00e5terstod bara <code>&amp;&amp;<\/code> operatorn.<\/p>\n<p><\/p>\n<p>Detta \u00e4r bara den centrala operationen. Vi m\u00e5ste alltid s\u00f6ka i hela huvudtabellen med URL:er f\u00f6r att s\u00f6ka efter m\u00f6nster, och vi m\u00e5ste alltid hitta \u00f6verlappningar. Vi kan inte s\u00f6ka i URL-poster direkt, eftersom det bara \u00e4r ID:n som h\u00e4nvisar till <code>urls<\/code>.<\/p>\n<p><\/p>\n<h2>P\u00e5 v\u00e4g mot en l\u00f6sning<\/h2>\n<p><\/p>\n<p><code>&amp;&amp;<\/code> l\u00e5ngsam, eftersom b\u00e5da upps\u00e4ttningarna \u00e4r enorma. Operationen kommer att vara relativt snabb om jag byter ut <code>urls<\/code> till <code>{ \"http:\/\/google.com\/\", \"http:\/\/wingify.com\/\" }<\/code>.<\/p>\n<p><\/p>\n<p>Jag b\u00f6rjade leta efter en metod f\u00f6r att g\u00f6ra upps\u00e4ttningsintersektioner i Postgres utan att anv\u00e4nda <code>&amp;&amp;<\/code>, men utan st\u00f6rre framg\u00e5ng.<\/p>\n<p><\/p>\n<p>Till slut beslutade vi oss f\u00f6r att bara l\u00f6sa problemet isolerat: ge mig alla <code>urls<\/code> rader, d\u00e4r URL:en matchar m\u00f6nstret. Utan n\u00e5gra ytterligare villkor blir det \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 OCH\n\turls.url ILIKE '%jobs%'<\/code><\/pre>\n<p><\/p>\n<p>Ist\u00e4llet f\u00f6r\u00a0<code>JOIN<\/code> s\u00e5 jag anv\u00e4nde bara en subfr\u00e5ga och rullade ut <code>recording_data.urls<\/code> arrayet, s\u00e5 att villkoret direkt kunde till\u00e4mpas i <code>D\u00c4R<\/code>.<\/p>\n<p><\/p>\n<p>Den viktiga punkten h\u00e4r \u00e4r att <code>&amp;&amp;<\/code> anv\u00e4nds f\u00f6r att kontrollera om den aktuella posten inneh\u00e5ller den \u00f6verensst\u00e4mmande URL:en. Om man kisar lite kan man se i denna operation en r\u00f6relse genom element i arrayen (eller rader i tabellen) och en stopp n\u00e4r villkoret (matchningen) uppfylls. K\u00e4nns det bekant? Ah-ha, <code>EXISTS<\/code>.<\/p>\n<p><\/p>\n<p>Eftersom p\u00e5 <code>recording_data.urls<\/code> kan refereras utanf\u00f6r kontexten av subfr\u00e5gan, n\u00e4r detta h\u00e4nder kan vi \u00e5terg\u00e5 till v\u00e5r gamla v\u00e4n <code>EXISTS<\/code> och omsluta den med en subfr\u00e5ga.<\/p>\n<p><\/p>\n<p>Sammanf\u00f6r allt f\u00e5r vi den slutliga optimerade fr\u00e5gan:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">V\u00c4LJ \n    count(*) \nFR\u00c5N \n    acc_{account_id}.urls som recordings_urls, \n    acc_{account_id}.recording_data som recording_data, \n    acc_{account_id}.sessions som sessions \nD\u00c4R \n    recording_data.usp_id = sessions.usp_id \n    OCH  (  1 = 1  )  \n    OCH sessions.referrer_id = recordings_urls.id \n    OCH r_time &gt; to_timestamp(1542585600) \n    OCH r_time =5 \n    OCH recording_data.num_of_pages &gt; 0\n    OCH FINNS(\n        V\u00c4LJ urls.url\n        FR\u00c5N \n            acc_{account_id}.urls som urls,\n            (V\u00c4LJ unnest(urls) SOM rec_url_id FR\u00c5N acc_{account_id}.recording_data) \n            SOM unrolled_urls\n        D\u00c4R\n            urls.id = unrolled_urls.rec_url_id OCH\n            urls.url  ILIKE  '%enterprise_customer.com\/jobs%'\n    );\n<\/code><\/pre>\n<p><\/p>\n<p>Och den slutgiltiga k\u00f6rningstiden <code>Tid: 1898.717 ms<\/code> \u00c4r det dags att fira?!?<\/p>\n<p><\/p>\n<p>Inte s\u00e5 snabbt! F\u00f6rst m\u00e5ste vi kontrollera korrektheten. Jag var extremt misst\u00e4nksam mot <code>EXISTS<\/code> optimeringen, eftersom den \u00e4ndrar logiken till en tidigare avslutning. Vi m\u00e5ste vara s\u00e4kra p\u00e5 att vi inte har lagt till ett subtilt fel i fr\u00e5gan.<\/p>\n<p><\/p>\n<p>Den enkla kontrollen bestod i att k\u00f6ra <code>count(*)<\/code> b\u00e5de p\u00e5 l\u00e5ngsamma och snabba fr\u00e5gor f\u00f6r en stor m\u00e4ngd olika datam\u00e4ngder. Sedan, f\u00f6r en liten delm\u00e4ngd av data, kontrollerade jag manuellt korrektheten av alla resultat.<\/p>\n<p><\/p>\n<p>Alla kontroller gav konsekvent positiva resultat. Vi har fixat allt!<\/p>\n<p><\/p>\n<h2>Dra slutsatser<\/h2>\n<p><\/p>\n<p>Det finns m\u00e5nga l\u00e4rdomar att dra fr\u00e5n denna historia:<\/p>\n<p><\/p>\n<ol>\n<li>Fr\u00e5geplaner ber\u00e4ttar inte hela historien, men kan ge ledtr\u00e5dar<\/li>\n<li>De fr\u00e4msta misst\u00e4nkta \u00e4r inte alltid de verkliga f\u00f6r\u00f6varna<\/li>\n<li>L\u00e5ngsamma fr\u00e5gor kan delas upp f\u00f6r att isolera flaskhalsar<\/li>\n<li>Inte all optimering \u00e4r per definition reducerande<\/li>\n<li>Anv\u00e4ndning <code>EXIST<\/code>, d\u00e4r det \u00e4r m\u00f6jligt, kan leda till en kraftig \u00f6kning av prestandan<\/li>\n<\/ol>\n<p><\/p>\n<h2>Utdata<\/h2>\n<p><\/p>\n<p>Vi gick fr\u00e5n en fr\u00e5getid p\u00e5 ~24 minuter till 2 sekunder \u2014 en mycket betydande prestanda\u00f6kning! \u00c4ven om denna artikel blev l\u00e5ng, intr\u00e4ffade alla experiment vi gjorde under en dag och uppskattades ta mellan 1,5 och 2 timmar f\u00f6r optimeringar och testning.<\/p>\n<p><\/p>\n<p>SQL \u00e4r ett fantastiskt spr\u00e5k, om man inte \u00e4r r\u00e4dd f\u00f6r det, utan ist\u00e4llet f\u00f6rs\u00f6ker f\u00f6rst\u00e5 och anv\u00e4nda det. Med god f\u00f6rst\u00e5else f\u00f6r hur SQL-fr\u00e5gor k\u00f6rs, hur databasen genererar fr\u00e5geplaner, hur index fungerar och helt enkelt den datastorlek man har att g\u00f6ra med, kan man lyckas riktigt bra med f\u00f6rfr\u00e5gningsoptimering. Det \u00e4r ocks\u00e5 viktigt att forts\u00e4tta prova olika tillv\u00e4gag\u00e5ngss\u00e4tt och l\u00e5ngsamt bryta ner problemet f\u00f6r att hitta flaskhalsar.<\/p>\n<p><\/p>\n<p>Den b\u00e4sta delen av att uppn\u00e5 s\u00e5dana resultat \u00e4r den m\u00e4rkbara f\u00f6rb\u00e4ttringen av hastigheten \u2014 n\u00e4r en rapport som tidigare inte ens kunde laddas nu n\u00e4stan laddas omedelbart.<\/p>\n<p><\/p>\n<p><strong>Stort tack\u00a0<\/strong>till mina kollegor\u00a0<em>i teamet Aditya Mishra<\/em>,\u00a0<em>Aditya Gaur\u00a0<\/em>och\u00a0<em><noindex><a rel=\"nofollow\" href=\"https:\/\/twitter.com\/s0ftvar\">Varun Malhotra\u00a0<\/a><\/noindex><\/em>f\u00f6r brainstorming och\u00a0<em>Dinkar Pandir\u00a0<\/em>f\u00f6r att ha hittat ett viktigt fel i v\u00e5r slutliga f\u00f6rfr\u00e5gan, innan vi slutligen skildes fr\u00e5n den!<\/p>\n<p>K\u00e4lla: <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.4 - 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\/sv\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"sv_SE\" \/>\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\/sv\/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\udd47En ber\u00e4ttelse om en SQL-utredning | ProHoster","description":"I december f\u00f6rra \u00e5ret fick jag en intressant felrapport fr\u00e5n VWO:s supportteam. Laddningstiden f\u00f6r en av analysrapporterna f\u00f6r en stor f\u00f6retagskund verkade orimligt l\u00e5ng.","canonical_url":"https:\/\/prohoster.info\/sv\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"sv_SE","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\/sv\/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\/sv\/wp-json\/wp\/v2\/posts\/35259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/comments?post=35259"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts\/35259\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media?parent=35259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/categories?post=35259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/tags?post=35259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}