{"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\/es\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","title":{"rendered":"La historia de una investigaci\u00f3n SQL","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>En diciembre del a\u00f1o pasado, recib\u00ed un informe interesante acerca de un error del equipo de soporte de VWO. El tiempo de carga de uno de los informes anal\u00edticos para un importante cliente corporativo parec\u00eda excesivamente largo. Y como esto es parte de mis responsabilidades, me centr\u00e9 de inmediato en resolver el problema.<\/p>\n<p><\/p>\n<h2>Antecedentes<\/h2>\n<p><\/p>\n<p>Para que est\u00e9 claro de qu\u00e9 se trata, contar\u00e9 un poco sobre VWO. Es una plataforma que permite lanzar diversas campa\u00f1as segmentadas en sus sitios web: realizar experimentos A\/B, rastrear visitantes y conversiones, hacer an\u00e1lisis de embudo de ventas, mostrar mapas de calor y reproducir grabaciones de visitas.<\/p>\n<p><\/p>\n<p>Pero lo m\u00e1s importante de la plataforma es la elaboraci\u00f3n de informes. Todas las funciones mencionadas est\u00e1n interrelacionadas. Para los clientes corporativos, un gran volumen de informaci\u00f3n ser\u00eda simplemente in\u00fatil sin una poderosa plataforma que la presente de manera anal\u00edtica.<\/p>\n<p><\/p>\n<p>Usando la plataforma, se puede realizar una consulta arbitraria sobre un gran conjunto de datos. Aqu\u00ed hay un ejemplo simple:<\/p>\n<p><\/p>\n<pre>Mostrar todos los clics en la p\u00e1gina \"abc.com\"\nDE &lt;fecha d1&gt; HASTA &lt;fecha d2&gt;\npara personas que\nusaron Chrome O\n(se encontraban en Europa Y usaron iPhone)<\/pre>\n<p><\/p>\n<p>Presta atenci\u00f3n a los operadores booleanos. Est\u00e1n disponibles para los clientes en la interfaz de consultas para hacer consultas tan complejas como deseen para obtener datos espec\u00edficos.<\/p>\n<p><\/p>\n<h2>Consulta lenta<\/h2>\n<p><\/p>\n<p>El cliente del que hablamos intentaba hacer algo que intuitivamente deber\u00eda funcionar r\u00e1pido:<\/p>\n<p><\/p>\n<pre>Muestre todos los registros de sesiones\npara usuarios que visitaron cualquier p\u00e1gina\ncon una URL que contenga \"\\\/jobs\"<\/pre>\n<p><\/p>\n<p>Este sitio ten\u00eda un gran volumen de tr\u00e1fico, y almacen\u00e1bamos m\u00e1s de un mill\u00f3n de URL \u00fanicas solo para \u00e9l. Y quer\u00edan encontrar un patr\u00f3n de URL bastante simple relacionado con su modelo de negocio.<\/p>\n<p>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Investigaci\u00f3n preliminar<\/h2>\n<p><\/p>\n<p>Veamos qu\u00e9 est\u00e1 sucediendo en la base de datos. Aqu\u00ed est\u00e1 la consulta SQL lenta original:<\/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>Y aqu\u00ed est\u00e1n los tiempos:<\/p>\n<p><\/p>\n<pre>Tiempo estimado: 1.480 ms\nTiempo de ejecuci\u00f3n: 1431924.650 ms<\/pre>\n<p><\/p>\n<p>La consulta recorri\u00f3 150 mil filas. El planificador de consultas mostr\u00f3 un par de detalles interesantes, pero ninguna zona de estrangulamiento obvia.<\/p>\n<p><\/p>\n<p>Vamos a investigar la consulta m\u00e1s a fondo. Como se puede ver, est\u00e1 haciendo <code>JOIN<\/code> tres tablas:<\/p>\n<p><\/p>\n<ol>\n<li><strong>sessions<\/strong>: para mostrar informaci\u00f3n de sesi\u00f3n: navegador, agente de usuario, pa\u00eds, y as\u00ed sucesivamente.<\/li>\n<li><strong>recording_data<\/strong>: URLs grabadas, p\u00e1ginas, duraci\u00f3n de visitas<\/li>\n<li><strong>urls<\/strong>: para evitar la duplicaci\u00f3n de URLs extremadamente grandes, las almacenamos en una tabla separada.<\/li>\n<\/ol>\n<p><\/p>\n<p>Tambi\u00e9n observe que todas nuestras tablas ya est\u00e1n divididas por <code>account_id<\/code>. De este modo, se evita la situaci\u00f3n en la que un cuenta especialmente grande cause problemas a las dem\u00e1s.<\/p>\n<p><\/p>\n<h2>En busca de pistas<\/h2>\n<p><\/p>\n<p>Al examinar detenidamente, vemos que hay algo extra\u00f1o en esta consulta en particular. Vale la pena mirar esta l\u00ednea:<\/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>El primer pensamiento fue que quiz\u00e1s, debido a <code>ILIKE<\/code> en todas estas URLs largas (tenemos m\u00e1s de 1.4 millones de <strong>URLs \u00fanicas, recopiladas para esta cuenta) el rendimiento podr\u00eda degradarse.\u00a0<\/strong>Pero no, \u00a1no se trata de eso!<\/p>\n<p><\/p>\n<p>SELECT id FROM urls WHERE url ILIKE '%enterprise_customer.com\/jobs%';\n  id\n--------\n ...\n(198661 filas)\n\nTiempo: 5231.765 ms<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">La consulta de b\u00fasqueda por patr\u00f3n tarda solo 5 segundos. Buscar por patr\u00f3n en un mill\u00f3n de URLs \u00fanicas claramente no es un problema.<\/code><\/pre>\n<p><\/p>\n<p>El siguiente sospechoso de la lista son varias<\/p>\n<p><\/p>\n<p>. \u00bfQuiz\u00e1s su uso excesivo est\u00e1 causando la lentitud? Normalmente <code>JOIN<\/code>\u2018s son los candidatos m\u00e1s obvios para problemas de rendimiento, pero no cre\u00eda que nuestro caso fuera t\u00edpico. <code>JOIN<\/code>\u2018Los problemas de rendimiento son los m\u00e1s evidentes, pero no cre\u00eda que nuestro caso fuera t\u00edpico.<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Y este tampoco fue nuestro caso.<\/code><\/pre>\n<p><\/p>\n<p>\u2018s resultaron ser bastante r\u00e1pidos. <code>JOIN<\/code>\u2018Resultaron ser bastante r\u00e1pidos.<\/p>\n<p><\/p>\n<h2>Estaba listo para comenzar a modificar la consulta para lograr cualquier mejora de rendimiento posible. Con el equipo, desarrollamos 2 ideas principales:<\/h2>\n<p><\/p>\n<p>Utilizar EXISTS para la subconsulta de URL<\/p>\n<p><\/p>\n<ul>\n<li><strong>: Quer\u00edamos verificar nuevamente si hab\u00eda problemas en la subconsulta para las URLs. Una forma de hacerlo es simplemente usar<\/strong>EXISTS <code>EXISTS<\/code>. <code>EXISTS<\/code> <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-subquery.html#FUNCTIONS-SUBQUERY-EXISTS\">puede<\/a><\/noindex> mejora significativamente el rendimiento ya que se detiene tan pronto como encuentra la \u00fanica fila que cumple la condici\u00f3n.<\/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>Bueno, s\u00ed. Una subconsulta, cuando est\u00e1 envuelta en\u00a0<code>EXISTS<\/code>, lo hace todo s\u00faper r\u00e1pido. La siguiente pregunta l\u00f3gica es, \u00bfpor qu\u00e9 la consulta con <code>JOIN<\/code>-es y la subconsulta en s\u00ed son r\u00e1pidas por separado, pero se ralentizan horriblemente juntas?<\/p>\n<p><\/p>\n<ul>\n<li><strong>Movemos la subconsulta a CTE <\/strong>: si la consulta es r\u00e1pida por s\u00ed sola, podemos simplemente calcular el resultado r\u00e1pido primero y luego proporcionarlo a la consulta principal<\/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>Pero a\u00fan as\u00ed, esto segu\u00eda siendo muy lento.<\/p>\n<p><\/p>\n<h2>Encontramos al culpable<\/h2>\n<p><\/p>\n<p>Durante todo este tiempo, hab\u00eda una peque\u00f1a cosa delante de mis ojos de la que siempre me deshac\u00eda. Pero como ya no quedaba nada m\u00e1s, decid\u00ed echar un vistazo a ella. Me refiero a <code>&amp;&amp;<\/code> el operador. Mientras que <code>EXISTS<\/code> simplemente mejor\u00f3 el rendimiento, <code>&amp;&amp;<\/code> era el \u00fanico factor com\u00fan restante en todas las versiones de la consulta lenta.<\/p>\n<p><\/p>\n<p>Mirando <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/9.1\/functions-array.html\">la documentaci\u00f3n<\/a><\/noindex>, vemos que <code>&amp;&amp;<\/code> se utiliza cuando se deben encontrar elementos comunes entre dos matrices.<\/p>\n<p><\/p>\n<p>En la consulta original esto 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>Lo que significa que estamos buscando un patr\u00f3n en nuestras URL, luego encontramos la intersecci\u00f3n con todas las URL con registros comunes. Esto es un poco confuso, ya que \"urls\" aqu\u00ed no se refiere a una tabla que contenga todas las URL, sino a la columna \"urls\" en la tabla <code>recording_data<\/code>.<\/p>\n<p><\/p>\n<p>Con el aumento de las sospechas hacia <code>&amp;&amp;<\/code>, trat\u00e9 de encontrarles confirmaci\u00f3n en el plan de consulta generado <code>EXPLAIN ANALYZE<\/code> (ya ten\u00eda un plan guardado, pero generalmente me resulta m\u00e1s f\u00e1cil experimentar en SQL que intentar comprender las opacidades de los planificadores de consultas).<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">Filtro: ((urls &amp;&amp; ($0)::text[]) Y (r_time &gt; '2018-12-17 12:17:23+00'::timestamp with time zone) Y (r_time = '5'::double precision) Y (num_of_pages &gt; 0))\n                           Filas eliminadas por el filtro: 52710<\/code><\/pre>\n<p><\/p>\n<p>Hab\u00eda varias l\u00edneas de filtros solo de <code>&amp;&amp;<\/code>. Lo que significaba que esta operaci\u00f3n no solo era costosa, sino que se ejecutaba varias veces.<\/p>\n<p><\/p>\n<p>Lo verifiqu\u00e9, aislando la condici\u00f3n<\/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>Esta consulta se ejecutaba lentamente. Dado que <code>JOIN<\/code>-s son r\u00e1pidos y las subconsultas r\u00e1pidas, quedaba solo <code>&amp;&amp;<\/code> el operador.<\/p>\n<p><\/p>\n<p>Solo esta es la operaci\u00f3n clave. Siempre necesitamos buscar en toda la tabla principal de URLs para buscar por patr\u00f3n, y siempre necesitamos encontrar intersecciones. No podemos buscar directamente en los registros de URLs porque son solo identificadores que se refieren a <code>urls<\/code>.<\/p>\n<p><\/p>\n<h2>En el camino hacia la soluci\u00f3n<\/h2>\n<p><\/p>\n<p><code>&amp;&amp;<\/code> lenta, porque ambos conjuntos son enormes. La operaci\u00f3n ser\u00e1 relativamente r\u00e1pida si reemplazo <code>urls<\/code> en <code>{ \"http:\/\/google.com\/\", \"http:\/\/wingify.com\/\" }<\/code>.<\/p>\n<p><\/p>\n<p>Comenc\u00e9 a buscar una forma de hacer en Postgres la intersecci\u00f3n de conjuntos sin usar <code>&amp;&amp;<\/code>, pero sin mucho \u00e9xito.<\/p>\n<p><\/p>\n<p>Al final, decidimos simplemente resolver el problema de forma aislada: dame todas las <code>urls<\/code> las l\u00edneas para las cuales la URL corresponde al patr\u00f3n. Sin condiciones adicionales, ser\u00e1 \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 Y\n\turls.url  ILIKE  '%jobs%'<\/code><\/pre>\n<p><\/p>\n<p>En lugar de\u00a0<code>JOIN<\/code> la sintaxis, simplemente us\u00e9 una subconsulta y descompuse <code>recording_data.urls<\/code> en un array, para poder aplicar la condici\u00f3n directamente en <code>WHERE<\/code>.<\/p>\n<p><\/p>\n<p>Lo m\u00e1s importante aqu\u00ed es que <code>&amp;&amp;<\/code> se utiliza para verificar si un registro dado contiene la URL correspondiente. Mirando de cerca, se puede ver en esta operaci\u00f3n el movimiento a trav\u00e9s de los elementos del array (o filas de la tabla) y la detenci\u00f3n al cumplir la condici\u00f3n (coincidencia). \u00bfNo recuerda algo? Ah, <code>EXISTS<\/code>.<\/p>\n<p><\/p>\n<p>Dado que en <code>recording_data.urls<\/code> se puede hacer referencia desde fuera del contexto de la subconsulta, cuando esto sucede, podemos volver a nuestro viejo amigo <code>EXISTS<\/code> y envolverlo en la subconsulta.<\/p>\n<p><\/p>\n<p>Uniendo todo, obtenemos la consulta final optimizada:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">SELECCIONAR \n    count(*) \nDE \n    acc_{account_id}.urls como recordings_urls, \n    acc_{account_id}.recording_data como recording_data, \n    acc_{account_id}.sessions como sessions \nDONDE \n    recording_data.usp_id = sessions.usp_id \n    Y  (  1 = 1  )  \n    Y sessions.referrer_id = recordings_urls.id \n    Y r_time &gt; to_timestamp(1542585600) \n    Y r_time = 5 \n    Y recording_data.num_of_pages &gt; 0\n    Y EXISTE(\n        SELECCIONAR urls.url\n        DE \n            acc_{account_id}.urls como urls,\n            (SELECCIONAR unnest(urls) COMO rec_url_id DE acc_{account_id}.recording_data) \n            COMO unrolled_urls\n        DONDE\n            urls.id = unrolled_urls.rec_url_id Y\n            urls.url ILIKE '%enterprise_customer.com\/j jobs%'\n    );\n<\/code><\/pre>\n<p><\/p>\n<p>Y el tiempo final de ejecuci\u00f3n <code>Tiempo: 1898.717 ms<\/code> \u00bfEs hora de celebrar?!?<\/p>\n<p><\/p>\n<p>\u00a1No tan r\u00e1pido! Primero hay que verificar la correcci\u00f3n. Fui extremadamente cauteloso con respecto a <code>EXISTS<\/code> la optimizaci\u00f3n, ya que cambia la l\u00f3gica hacia un final m\u00e1s temprano. Debemos asegurarnos de que no hemos a\u00f1adido un error no evidente en la consulta.<\/p>\n<p><\/p>\n<p>Una verificaci\u00f3n simple consisti\u00f3 en ejecutar <code>count(*)<\/code> tanto en consultas lentas como r\u00e1pidas para una gran variedad de conjuntos de datos. Luego, para un peque\u00f1o subconjunto de datos, verifiqu\u00e9 manualmente la precisi\u00f3n de todos los resultados.<\/p>\n<p><\/p>\n<p>Todas las verificaciones dieron resultados consistentemente positivos. \u00a1Lo hemos arreglado todo!<\/p>\n<p><\/p>\n<h2>Lecciones Aprendidas<\/h2>\n<p><\/p>\n<p>De esta historia se pueden extraer muchas lecciones:<\/p>\n<p><\/p>\n<ol>\n<li>Los planes de consulta no cuentan toda la historia, pero pueden dar pistas<\/li>\n<li>Los principales sospechosos no siempre son los verdaderos culpables<\/li>\n<li>Las consultas lentas se pueden dividir para aislar cuellos de botella<\/li>\n<li>No todas las optimizaciones son inherentemente reductivas<\/li>\n<li>Uso <code>EXIST<\/code>, donde sea posible, puede llevar a un aumento dr\u00e1stico en el rendimiento<\/li>\n<\/ol>\n<p><\/p>\n<h2>Salida<\/h2>\n<p><\/p>\n<p>Hemos pasado de un tiempo de consulta de ~24 minutos a 2 segundos \u2014 \u00a1un aumento de rendimiento bastante significativo! Aunque este art\u00edculo es extenso, todos los experimentos que realizamos ocurrieron en un solo d\u00eda, y se estima que tomaron entre 1.5 y 2 horas para optimizaciones y pruebas.<\/p>\n<p><\/p>\n<p>SQL es un lenguaje maravilloso, si no se le teme, sino que se intenta comprender y utilizar. Teniendo una buena comprensi\u00f3n de c\u00f3mo se ejecutan las consultas SQL, c\u00f3mo la base de datos genera planes de consulta, c\u00f3mo funcionan los \u00edndices y simplemente el tama\u00f1o de los datos con los que se trata, se puede tener mucho \u00e9xito en la optimizaci\u00f3n de consultas. Sin embargo, igualmente importante es seguir probando diferentes enfoques y dividir lentamente el problema, encontrando cuellos de botella.<\/p>\n<p><\/p>\n<p>La mejor parte de lograr tales resultados es la notable mejora visible en la velocidad de trabajo: un informe que antes ni siquiera se cargaba, ahora se carga casi instant\u00e1neamente.<\/p>\n<p><\/p>\n<p><strong>Agradecimientos especiales\u00a0<\/strong>a mis compa\u00f1eros\u00a0<em>del equipo Aditya Mishra<\/em>,\u00a0<em>Aditya Gaur\u00a0<\/em>y\u00a0<em><noindex><a rel=\"nofollow\" href=\"https:\/\/twitter.com\/s0ftvar\">Varun Malhotra\u00a0<\/a><\/noindex><\/em>por el brainstorming y\u00a0<em>Dinkar Pandir\u00a0<\/em>por encontrar un error importante en nuestra solicitud final, \u00a1antes de que nos despidi\u00e9ramos de ella!<\/p>\n<p>Fuente: <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\/es\/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=\"es_ES\" \/>\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\/es\/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\udd47La historia de una investigaci\u00f3n SQL | ProHoster","description":"En diciembre del a\u00f1o pasado, recib\u00ed un interesante informe de error del equipo de soporte de VWO. El tiempo de carga de uno de los informes anal\u00edticos para un gran cliente corporativo parec\u00eda excesivamente alto.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/istoriya-odnogo-sql-rassledovaniya","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","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\/es\/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\/es\/wp-json\/wp\/v2\/posts\/35259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=35259"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/35259\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=35259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=35259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=35259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}