{"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\/fr\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","title":{"rendered":"L'histoire d'une enqu\u00eate SQL","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>En d\u00e9cembre de l'ann\u00e9e derni\u00e8re, j'ai re\u00e7u un rapport int\u00e9ressant sur une erreur de l'\u00e9quipe de support VWO. Le temps de chargement d'un des rapports analytiques pour un grand client entreprise semblait excessivement long. \u00c9tant donn\u00e9 que cela rel\u00e8ve de ma responsabilit\u00e9, je me suis imm\u00e9diatement concentr\u00e9 sur la r\u00e9solution du probl\u00e8me.<\/p>\n<p><\/p>\n<h2>Contexte<\/h2>\n<p><\/p>\n<p>Pour comprendre de quoi il s'agit, je vais parler un peu de VWO. C'est une plateforme qui permet de lancer diff\u00e9rentes campagnes cibl\u00e9es sur ses sites : r\u00e9aliser des exp\u00e9riences A\/B, suivre les visiteurs et les conversions, analyser l'entonnoir de vente, afficher des cartes de chaleur et reproduire des enregistrements de visites.<\/p>\n<p><\/p>\n<p>Mais ce qui est le plus important dans la plateforme, c'est la cr\u00e9ation de rapports. Toutes les fonctions mentionn\u00e9es ci-dessus sont interconnect\u00e9es. Et pour les clients entreprise, un grand volume d'informations serait tout simplement inutile sans une plateforme puissante qui les pr\u00e9sente sous forme d'analytique.<\/p>\n<p><\/p>\n<p>En utilisant la plateforme, il est possible de faire une requ\u00eate libre sur un grand ensemble de donn\u00e9es. Voici un exemple simple :<\/p>\n<p><\/p>\n<pre>Afficher tous les clics sur la page \"abc.com\"\nDE &lt;date d1&gt; \u00c0 &lt;date d2&gt;\npour les personnes qui\nutilisaient Chrome OU\n(\u00e9taient en Europe ET utilisaient un iPhone)<\/pre>\n<p><\/p>\n<p>Notez les op\u00e9rateurs bool\u00e9ens. Ils sont disponibles pour les clients dans l'interface de requ\u00eate afin de r\u00e9aliser des requ\u00eates aussi complexes que souhait\u00e9 pour obtenir des \u00e9chantillons.<\/p>\n<p><\/p>\n<h2>Requ\u00eate lente<\/h2>\n<p><\/p>\n<p>Le client en question essayait de faire quelque chose qui, selon l'intuition, devrait fonctionner rapidement :<\/p>\n<p><\/p>\n<pre>Montrez tous les enregistrements de sessions\npour les utilisateurs ayant visit\u00e9 n'importe quelle page\navec une URL contenant \"\/jobs\"<\/pre>\n<p><\/p>\n<p>Ce site avait un \u00e9norme trafic, et nous stockions plus d'un million d'URL uniques juste pour lui. Ils voulaient trouver un mod\u00e8le d'URL assez simple, pertinent pour leur mod\u00e8le commercial.<\/p>\n<p>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Enqu\u00eate pr\u00e9liminaire<\/h2>\n<p><\/p>\n<p>Regardons ce qui se passe dans la base de donn\u00e9es. Voici la requ\u00eate SQL lente originale :<\/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>Voici les temps :<\/p>\n<p><\/p>\n<pre>Temps pr\u00e9vu : 1,480 ms\nTemps d'ex\u00e9cution : 1,431,924.650 ms<\/pre>\n<p><\/p>\n<p>La requ\u00eate a parcouru 150 000 lignes. Le planificateur de requ\u00eates a montr\u00e9 quelques d\u00e9tails int\u00e9ressants, mais aucune goulot d'\u00e9tranglement \u00e9vident.<\/p>\n<p><\/p>\n<p>Continuons \u00e0 examiner la requ\u00eate. Comme on peut le voir, elle effectue <code>JOIN<\/code> trois tables :<\/p>\n<p><\/p>\n<ol>\n<li><strong>sessions<\/strong>: pour afficher les informations de session : navigateur, agent utilisateur, pays, etc.<\/li>\n<li><strong>recording_data<\/strong>: URLs enregistr\u00e9es, pages, dur\u00e9e des visites<\/li>\n<li><strong>urls<\/strong>: pour \u00e9viter la duplication d'URLs exceptionnellement longues, nous les stockons dans une table s\u00e9par\u00e9e.<\/li>\n<\/ol>\n<p><\/p>\n<p>Remarquez \u00e9galement que toutes nos tables sont d\u00e9j\u00e0 divis\u00e9es par <code>account_id<\/code>. Ainsi, il est \u00e9vit\u00e9 qu'en raison d'un compte particuli\u00e8rement volumineux, des probl\u00e8mes se produisent pour les autres.<\/p>\n<p><\/p>\n<h2>\u00c0 la recherche d'indices<\/h2>\n<p><\/p>\n<p>En y regardant de plus pr\u00e8s, nous voyons qu'il y a quelque chose d'anormal dans cette requ\u00eate sp\u00e9cifique. Il vaut la peine de porter attention \u00e0 cette ligne :<\/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>La premi\u00e8re pens\u00e9e \u00e9tait que peut-\u00eatre \u00e0 cause de <code>ILIKE<\/code> sur toutes ces longues URLs (nous avons plus de 1,4 million de <strong>URLs uniques rassembl\u00e9es pour ce compte) la performance pourrait \u00eatre impact\u00e9e.\u00a0<\/strong>Mais non \u2014 ce n'est pas \u00e7a !<\/p>\n<p><\/p>\n<p>SELECT id FROM urls WHERE url ILIKE '%enterprise_customer.com\/jobs%';\n  id\n--------\n ...\n(198661 lignes)\n\nTemps : 5231.765 ms<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">La requ\u00eate de recherche par motif ne prend que 5 secondes. La recherche par motif sur un million d'URLs uniques n'est manifestement pas un probl\u00e8me.<\/code><\/pre>\n<p><\/p>\n<p>Le prochain suspect sur la liste \u2014 quelques<\/p>\n<p><\/p>\n<p>. Peut-\u00eatre leur utilisation excessive a-t-elle contribu\u00e9 \u00e0 ralentir ? Habituellement <code>JOIN<\/code>\u2018s sont les candidats les plus \u00e9vidents pour les probl\u00e8mes de performance, mais je ne croyais pas que notre cas \u00e9tait typique. <code>JOIN<\/code>Les 'a' sont les candidats les plus \u00e9vidents aux probl\u00e8mes de performance, mais je ne croyais pas que notre cas \u00e9tait typique.<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Et cela n'\u00e9tait pas notre cas non plus.<\/code><\/pre>\n<p><\/p>\n<p>Les \u2018s se sont r\u00e9v\u00e9l\u00e9s tr\u00e8s rapides. <code>JOIN<\/code>Les 'a' se sont r\u00e9v\u00e9l\u00e9s plut\u00f4t rapides.<\/p>\n<p><\/p>\n<h2>J'\u00e9tais pr\u00eat \u00e0 commencer \u00e0 modifier la requ\u00eate pour atteindre des am\u00e9liorations de performance possibles. Avec l'\u00e9quipe, nous avons \u00e9labor\u00e9 2 id\u00e9es principales :<\/h2>\n<p><\/p>\n<p>Utiliser EXISTS pour la sous-requ\u00eate URL<\/p>\n<p><\/p>\n<ul>\n<li><strong>: Nous voulions v\u00e9rifier \u00e0 nouveau s'il n'y avait pas de probl\u00e8mes avec la sous-requ\u00eate pour les URLs. Une des fa\u00e7ons d'y parvenir est simplement de utiliser<\/strong>: Nous voulions v\u00e9rifier \u00e0 nouveau s'il y avait des probl\u00e8mes avec la requ\u00eate pour les URL. L'une des fa\u00e7ons d'y parvenir est simplement d'utiliser <code>soient \u00ab vrais \u00bb, mais<\/code>. <code>soient \u00ab vrais \u00bb, mais<\/code> <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS\">peut<\/a><\/noindex> am\u00e9liorer consid\u00e9rablement les performances car il se termine d\u00e8s qu'il trouve la premi\u00e8re ligne correspondant \u00e0 la condition.<\/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>Eh bien. La sous-requ\u00eate, lorsqu'elle est encapsul\u00e9e dans\u00a0<code>soient \u00ab vrais \u00bb, mais<\/code>, rend tout super rapide. La question logique suivante est : pourquoi la requ\u00eate avec <code>JOIN<\/code>-s et la sous-requ\u00eate elle-m\u00eame sont rapides s\u00e9par\u00e9ment, mais ralentissent terriblement ensemble ?<\/p>\n<p><\/p>\n<ul>\n<li><strong>D\u00e9pla\u00e7ons la sous-requ\u00eate dans un CTE <\/strong>: si la requ\u00eate est rapide en elle-m\u00eame, nous pouvons simplement d'abord calculer le r\u00e9sultat rapide, puis le fournir \u00e0 la requ\u00eate principale.<\/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>Mais cela restait encore tr\u00e8s lent.<\/p>\n<p><\/p>\n<h2>Trouvons le coupable<\/h2>\n<p><\/p>\n<p>Tout ce temps, une petite chose me trottait dans la t\u00eate \u00e0 laquelle je me suis constamment d\u00e9rob\u00e9. Mais puisque je n'avais plus rien d'autre, j'ai d\u00e9cid\u00e9 de m'y attarder. Je parle de <code>&amp;&amp;<\/code> l'op\u00e9rateur. Pendant que <code>soient \u00ab vrais \u00bb, mais<\/code> a simplement am\u00e9lior\u00e9 les performances, <code>&amp;&amp;<\/code> \u00e9tait le seul facteur commun restant dans toutes les versions de la requ\u00eate lente.<\/p>\n<p><\/p>\n<p>En regardant <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/9.1\/functions-array.html\">documentation<\/a><\/noindex>, nous voyons que <code>&amp;&amp;<\/code> est utilis\u00e9 lorsque nous devons trouver des \u00e9l\u00e9ments communs entre deux tableaux.<\/p>\n<p><\/p>\n<p>Dans la requ\u00eate originale, c'est :<\/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>Ce qui signifie que nous effectuons une recherche par motif sur nos URLs, puis trouvons l'intersection avec toutes les URLs ayant des enregistrements en commun. C'est un peu confus, car \u00ab urls \u00bb ici ne fait pas r\u00e9f\u00e9rence \u00e0 la table contenant toutes les adresses URL, mais \u00e0 la colonne \u00ab urls \u00bb dans la table <code>recording_data<\/code>.<\/p>\n<p><\/p>\n<p>Avec des soup\u00e7ons grandissants envers <code>&amp;&amp;<\/code>, j'ai tent\u00e9 de les confirmer dans le plan de requ\u00eate g\u00e9n\u00e9r\u00e9. <code>EXPLAIN ANALYZE<\/code> (j'avais d\u00e9j\u00e0 un plan enregistr\u00e9, mais je pr\u00e9f\u00e8re g\u00e9n\u00e9ralement exp\u00e9rimenter avec SQL plut\u00f4t que d'essayer de comprendre les opacit\u00e9s des planificateurs de requ\u00eates).<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Filtre : ((urls &amp;&amp; ($0)::text[]) ET (r_time &gt; '2018-12-17 12:17:23+00'::timestamp with time zone) ET (r_time = '5'::double precision) ET (num_of_pages &gt; 0))\n                           Lignes supprim\u00e9es par le filtre : 52710<\/code><\/pre>\n<p><\/p>\n<p>Il y avait plusieurs lignes de filtres uniquement depuis <code>&amp;&amp;<\/code>. Ce qui signifiait que cette op\u00e9ration non seulement co\u00fbtait cher, mais \u00e9tait \u00e9galement ex\u00e9cut\u00e9e plusieurs fois.<\/p>\n<p><\/p>\n<p>J'ai v\u00e9rifi\u00e9 cela en isolant la condition<\/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>Cette requ\u00eate s'ex\u00e9cutait lentement. Puisque <code>JOIN<\/code>-s sont rapides et les sous-requ\u00eates sont rapides, il ne restait que <code>&amp;&amp;<\/code> l'op\u00e9rateur.<\/p>\n<p><\/p>\n<p>C'est juste que c'est l'op\u00e9ration cl\u00e9. Nous devons toujours rechercher dans l'ensemble principal des URLs pour rechercher par motif, et nous devons toujours trouver des intersections. Nous ne pouvons pas rechercher directement dans les enregistrements d'urls, car ce ne sont que des identifiants faisant r\u00e9f\u00e9rence \u00e0 <code>urls<\/code>.<\/p>\n<p><\/p>\n<h2>En chemin vers la solution<\/h2>\n<p><\/p>\n<p><code>&amp;&amp;<\/code> lente, car les deux ensembles sont \u00e9normes. L'op\u00e9ration sera relativement rapide si je remplace <code>urls<\/code> sur <code>{ \"http:\/\/google.com\/\", \"http:\/\/wingify.com\/\" }<\/code>.<\/p>\n<p><\/p>\n<p>J'ai commenc\u00e9 \u00e0 chercher un moyen de faire en Postgres l'intersection de plusieurs ensembles sans utiliser <code>&amp;&amp;<\/code>, mais sans grand succ\u00e8s.<\/p>\n<p><\/p>\n<p>Finalement, nous avons d\u00e9cid\u00e9 de simplement r\u00e9soudre le probl\u00e8me de mani\u00e8re isol\u00e9e : donne-moi toutes les <code>urls<\/code> des lignes dont l'URL correspond au mod\u00e8le. Sans conditions suppl\u00e9mentaires, cela sera \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 AND\n\turls.url  ILIKE  '%jobs%'<\/code><\/pre>\n<p><\/p>\n<p>Au lieu de\u00a0<code>JOIN<\/code> syntaxe, j'ai simplement utilis\u00e9 une sous-requ\u00eate et d\u00e9ball\u00e9 <code>recording_data.urls<\/code> le tableau, afin qu'il soit possible d'appliquer directement la condition dans <code>O\u00d9<\/code>.<\/p>\n<p><\/p>\n<p>Ce qui est le plus important ici, c'est que <code>&amp;&amp;<\/code> est utilis\u00e9 pour v\u00e9rifier si cette entr\u00e9e contient l'URL correspondante. En plissant un peu les yeux, on peut voir dans cette op\u00e9ration le d\u00e9placement \u00e0 travers les \u00e9l\u00e9ments du tableau (ou les lignes d'une table) et l'arr\u00eat lors de l'ex\u00e9cution de la condition (correspondance). \u00c7a ne vous rappelle rien ? Ah, <code>soient \u00ab vrais \u00bb, mais<\/code>.<\/p>\n<p><\/p>\n<p>Puisque sur <code>recording_data.urls<\/code> peut \u00eatre r\u00e9f\u00e9renc\u00e9 en dehors du contexte de la sous-requ\u00eate, lorsqu'elle se produit, nous pouvons revenir \u00e0 notre vieux camarade <code>soient \u00ab vrais \u00bb, mais<\/code> et l'envelopper dans la sous-requ\u00eate.<\/p>\n<p><\/p>\n<p>En combinant le tout, nous obtenons la requ\u00eate optimis\u00e9e finale :<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">S\u00c9LECTIONNER \n    COUNT(*) \nDE \n    acc_{account_id}.urls comme recordings_urls, \n    acc_{account_id}.recording_data comme recording_data, \n    acc_{account_id}.sessions comme sessions \nO\u00d9 \n    recording_data.usp_id = sessions.usp_id \n    ET (  1 = 1  )  \n    ET sessions.referrer_id = recordings_urls.id \n    ET r_time &gt; to_timestamp(1542585600) \n    ET r_time =5 \n    ET recording_data.num_of_pages &gt; 0\n    ET EXISTE(\n        S\u00c9LECTIONNER urls.url\n        DE \n            acc_{account_id}.urls comme urls,\n            (S\u00c9LECTIONNER unnest(urls) COMME rec_url_id DE acc_{account_id}.recording_data) \n            COMME unrolled_urls\n        O\u00d9\n            urls.id = unrolled_urls.rec_url_id ET\n            urls.url  ILIKE  '%enterprise_customer.com\/jobs%'\n    );\n<\/code><\/pre>\n<p><\/p>\n<p>Et le temps d'ex\u00e9cution final <code>Temps : 1898,717 ms<\/code> Il est temps de c\u00e9l\u00e9brer?!?<\/p>\n<p><\/p>\n<p>Pas si vite ! D'abord, il faut v\u00e9rifier la validit\u00e9. J'\u00e9tais extr\u00eamement suspicieux concernant <code>soient \u00ab vrais \u00bb, mais<\/code> l'optimisation, car elle modifie la logique pour une fin anticip\u00e9e. Nous devons \u00eatre s\u00fbrs de ne pas avoir introduit une erreur subtile dans la requ\u00eate.<\/p>\n<p><\/p>\n<p>La simple v\u00e9rification consistait \u00e0 ex\u00e9cuter <code>count(*)<\/code> \u00e0 la fois sur des requ\u00eates lentes et rapides pour un large \u00e9ventail de jeux de donn\u00e9es. Ensuite, pour un petit sous-ensemble de donn\u00e9es, j'ai v\u00e9rifi\u00e9 manuellement l'exactitude de tous les r\u00e9sultats.<\/p>\n<p><\/p>\n<p>Tous les tests ont donn\u00e9 des r\u00e9sultats positifs stables. Tout est r\u00e9par\u00e9 !<\/p>\n<p><\/p>\n<h2>Le\u00e7ons tir\u00e9es<\/h2>\n<p><\/p>\n<p>Il y a beaucoup de le\u00e7ons \u00e0 tirer de cette histoire :<\/p>\n<p><\/p>\n<ol>\n<li>Les plans de requ\u00eates ne racontent pas toute l'histoire, mais peuvent donner des indices<\/li>\n<li>Les principaux suspects ne sont pas toujours les v\u00e9ritables coupables<\/li>\n<li>Les requ\u00eates lentes peuvent \u00eatre d\u00e9compos\u00e9es pour isoler les goulets d'\u00e9tranglement<\/li>\n<li>Toutes les optimisations ne sont pas par nature r\u00e9ductrices<\/li>\n<li>Utilisation <code>EXIST<\/code>, l\u00e0 o\u00f9 c'est possible, cela peut conduire \u00e0 une augmentation radicale des performances<\/li>\n<\/ol>\n<p><\/p>\n<h2>Sortie<\/h2>\n<p><\/p>\n<p>Nous sommes pass\u00e9s d'un temps de requ\u00eate d'environ 24 minutes \u00e0 2 secondes \u2014 une augmentation de performance assez s\u00e9rieuse ! Bien que cet article soit long, toutes les exp\u00e9riences que nous avons men\u00e9es se sont d\u00e9roul\u00e9es en une journ\u00e9e et ont pris entre 1,5 et 2 heures pour les optimisations et les tests.<\/p>\n<p><\/p>\n<p>SQL est un merveilleux langage, si l'on n'en a pas peur, mais que l'on cherche \u00e0 le comprendre et \u00e0 l'utiliser. Avec une bonne compr\u00e9hension de la fa\u00e7on dont les requ\u00eates SQL sont ex\u00e9cut\u00e9es, de la mani\u00e8re dont la base de donn\u00e9es g\u00e9n\u00e8re des plans de requ\u00eate, de la fa\u00e7on dont les index fonctionnent et simplement de la taille des donn\u00e9es avec lesquelles vous avez affaire, vous pourrez vraiment exceller dans l'optimisation des requ\u00eates. Il est aussi crucial de continuer \u00e0 essayer diff\u00e9rentes approches et \u00e0 d\u00e9composer progressivement le probl\u00e8me, en identifiant les goulets d'\u00e9tranglement.<\/p>\n<p><\/p>\n<p>La meilleure partie d'obtenir de tels r\u00e9sultats est l'am\u00e9lioration visible et significative de la vitesse \u2014 lorsque le rapport qui ne se chargeait m\u00eame pas auparavant se charge maintenant presque instantan\u00e9ment.<\/p>\n<p><\/p>\n<p><strong>Un remerciement particulier\u00a0<\/strong>\u00e0 mes coll\u00e8gues\u00a0<em>de l'\u00e9quipe Aditya Mishra<\/em>,\u00a0<em>Aditya Gaur\u00a0<\/em>et\u00a0<em><noindex><a rel=\"nofollow\" href=\"https:\/\/twitter.com\/s0ftvar\">Varun Malhotra\u00a0<\/a><\/noindex><\/em>pour le brainstorming et\u00a0<em>Dinkar Pandir\u00a0<\/em>pour avoir trouv\u00e9 une erreur importante dans notre requ\u00eate finale, avant que nous ne nous en s\u00e9parions d\u00e9finitivement !<\/p>\n<p>Source : <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.2 - 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\/fr\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"fr_FR\" \/>\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\/fr\/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\udd47L'histoire d'une enqu\u00eate SQL | ProHoster","description":"En d\u00e9cembre dernier, j'ai re\u00e7u un rapport d'erreur int\u00e9ressant de l'\u00e9quipe d'assistance VWO. Le temps de chargement d'un des rapports analytiques pour un grand client corporate semblait d\u00e9mesur\u00e9.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","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\/fr\/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\/fr\/wp-json\/wp\/v2\/posts\/35259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=35259"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/35259\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=35259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=35259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=35259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}