sql >> Base de Datos >  >> RDS >> PostgreSQL

Obtenga el último hijo por padre de la tabla grande:la consulta es demasiado lenta

Lo más importante es que JOIN y GROUP sobre todo solo para obtener max(created) . Obtenga este valor por separado.

Mencionaste todos los índices que se necesitan aquí:en report_rank.created y en las claves foráneas. Lo estás haciendo bien allí. (Si está interesado en algo mejor que "bien", siga leyendo !)

El LEFT JOIN report_site se verá obligado a un simple JOIN por el WHERE cláusula. Sustituí un simple JOIN . También simplifiqué mucho tu sintaxis.

Actualizado en julio de 2015 con consultas más simples y rápidas y funciones más inteligentes.

Solución para varias filas

report_rank.created es no único y quieres todo las últimas filas.
Usando la función de ventana rank() en una subconsulta.

SELECT r.id, r.keyword_id, r.site_id
     , r.rank, r.url, r.competition
     , r.source, r.country, r.created  -- same as "max"
FROM  (
   SELECT *, rank() OVER (ORDER BY created DESC NULLS LAST) AS rnk
   FROM   report_rank r
   WHERE  EXISTS (
      SELECT *
      FROM   report_site    s
      JOIN   report_profile p ON p.site_id = s.id
      JOIN   crm_client     c ON c.id      = p.client_id
      JOIN   auth_user      u ON u.id      = c.user_id
      WHERE  s.id = r.site_id
      AND    u.is_active
      AND    c.is_deleted = FALSE
      )
   ) sub
WHERE  rnk = 1;

Por qué DESC NULLS LAST ?

Solución para una fila

Si report_rank.created es único o está satisfecho con cualquier fila con max(created) :

SELECT id, keyword_id, site_id
     , rank, url, competition
     , source, country, created  -- same as "max"
FROM   report_rank r
WHERE  EXISTS (
    SELECT 1
    FROM   report_site    s
    JOIN   report_profile p ON p.site_id = s.id
    JOIN   crm_client     c ON c.id      = p.client_id
    JOIN   auth_user      u ON u.id      = c.user_id
    WHERE  s.id = r.site_id
    AND    u.is_active
    AND    c.is_deleted = FALSE
   )
-- AND  r.created > f_report_rank_cap()
ORDER  BY r.created DESC NULLS LAST
LIMIT  1;

Debería ser más rápido, aún. Más opciones:

Ultimate Speed ​​con índice parcial ajustado dinámicamente

Es posible que haya notado la parte comentada en la última consulta:

AND  r.created > f_report_rank_cap()

Mencionaste 50 millones. filas, eso es mucho. Aquí hay una manera de acelerar las cosas:

  • Cree un IMMUTABLE simple función que devuelve una marca de tiempo que garantiza que es más antigua que las filas de interés y que es lo más joven posible.
  • Cree un índice parcial solo en filas más jóvenes, según esta función.
  • Use un WHERE condición en las consultas que coincide con la condición del índice.
  • Cree otra función que actualice estos objetos a la fila más reciente con DDL dinámico. (Menos un margen seguro en caso de que las filas más nuevas se eliminen o desactiven, si eso puede suceder)
  • Invoque esta función secundaria en momentos de inactividad con un mínimo de actividad simultánea por cronjob o bajo demanda. Con la frecuencia que desee, no puede hacer daño, solo necesita un breve bloqueo exclusivo en la mesa.

Aquí hay una demostración de trabajo completa .
@erikcw, deberá activar la parte comentada como se indica a continuación.

CREATE TABLE report_rank(created timestamp);
INSERT INTO report_rank VALUES ('2011-11-11 11:11'),(now());

-- initial function
CREATE OR REPLACE FUNCTION f_report_rank_cap()
  RETURNS timestamp LANGUAGE sql COST 1 IMMUTABLE AS
$y$SELECT timestamp '-infinity'$y$;  -- or as high as you can safely bet.

-- initial index; 1st run indexes whole tbl if starting with '-infinity'
CREATE INDEX report_rank_recent_idx ON report_rank (created DESC NULLS LAST)
WHERE  created > f_report_rank_cap();

-- function to update function & reindex
CREATE OR REPLACE FUNCTION f_report_rank_set_cap()
  RETURNS void AS
$func$
DECLARE
   _secure_margin CONSTANT interval := interval '1 day';  -- adjust to your case
   _cap timestamp;  -- exclude older rows than this from partial index
BEGIN
   SELECT max(created) - _secure_margin
   FROM   report_rank
   WHERE  created > f_report_rank_cap() + _secure_margin
   /*  not needed for the demo; @erikcw needs to activate this
   AND    EXISTS (
     SELECT *
     FROM   report_site    s
     JOIN   report_profile p ON p.site_id = s.id
     JOIN   crm_client     c ON c.id      = p.client_id
     JOIN   auth_user      u ON u.id      = c.user_id
     WHERE  s.id = r.site_id
     AND    u.is_active
     AND    c.is_deleted = FALSE)
   */
   INTO   _cap;

   IF FOUND THEN
     -- recreate function
     EXECUTE format('
     CREATE OR REPLACE FUNCTION f_report_rank_cap()
       RETURNS timestamp LANGUAGE sql IMMUTABLE AS
     $y$SELECT %L::timestamp$y$', _cap);

     -- reindex
     REINDEX INDEX report_rank_recent_idx;
   END IF;
END
$func$  LANGUAGE plpgsql;

COMMENT ON FUNCTION f_report_rank_set_cap()
IS 'Dynamically recreate function f_report_rank_cap()
    and reindex partial index on report_rank.';

Llamar:

SELECT f_report_rank_set_cap();

Ver:

SELECT f_report_rank_cap();

Descomente la cláusula AND r.created > f_report_rank_cap() en la consulta anterior y observe la diferencia. Verifique que el índice se use con EXPLAIN ANALYZE .

El manual sobre concurrencia y REINDEX :