PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Holen Sie sich das neueste Kind pro Elternteil aus der großen Tabelle - die Abfrage ist zu langsam

Der wichtigste Punkt ist höchstwahrscheinlich, dass Sie JOIN werden und GROUP über alles, nur um max(created) zu erhalten . Holen Sie sich diesen Wert separat.

Sie haben alle Indizes erwähnt, die hier benötigt werden:auf report_rank.created und auf den Fremdschlüsseln. Da geht es dir gut. (Wenn Sie an besser als "in Ordnung" interessiert sind, lesen Sie weiter !)

Die LEFT JOIN report_site wird zu einem einfachen JOIN gezwungen durch das WHERE Klausel. Ich habe ein einfaches JOIN ersetzt . Ich habe auch Ihre Syntax stark vereinfacht.

Aktualisiert im Juli 2015 mit einfacheren, schnelleren Abfragen und intelligenteren Funktionen.

Lösung für mehrere Zeilen

report_rank.created ist nicht eindeutig und Sie wollen alle die neuesten Zeilen.
Mit der Fensterfunktion rank() in einer Unterabfrage.

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;

Warum DESC NULLS LAST ?

Lösung für eine Reihe

Wenn report_rank.created ist einzigartig oder Sie sind mit einer beliebigen Zeile zufrieden mit 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;

Sollte immer noch schneller sein. Weitere Optionen:

Ultimate Speed ​​mit dynamisch angepasstem Teilindex

Möglicherweise ist Ihnen der kommentierte Teil in der letzten Abfrage aufgefallen:

AND  r.created > f_report_rank_cap()

Sie haben 50 Mio. Reihen, das ist eine Menge. Hier ist eine Möglichkeit, die Dinge zu beschleunigen:

  • Erstellen Sie einen einfachen IMMUTABLE Funktion, die einen Zeitstempel zurückgibt, der garantiert älter als die relevanten Zeilen ist, aber so jung wie möglich.
  • Erstellen Sie einen Teilindex nur in jüngeren Zeilen - basierend auf dieser Funktion.
  • Verwenden Sie ein WHERE Bedingung in Abfragen, die der Indexbedingung entspricht.
  • Erstellen Sie eine weitere Funktion, die diese Objekte mit dynamischer DDL auf die neueste Zeile aktualisiert. (Abzüglich einer sicheren Marge falls die neueste(n) Zeile(n) gelöscht / deaktiviert werden - falls das passieren kann)
  • Rufen Sie diese sekundäre Funktion in Ruhezeiten mit einem Minimum an gleichzeitiger Aktivität per Cronjob oder bei Bedarf auf. So oft du willst, kann nicht schaden, es braucht nur eine kurze exklusive Sperre auf dem Tisch.

Hier ist eine vollständig funktionierende Demo .
@erikcw, Sie müssen den kommentierten Teil wie unten beschrieben aktivieren.

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.';

Aufruf:

SELECT f_report_rank_set_cap();

Siehe:

SELECT f_report_rank_cap();

Entkommentieren Sie die Klausel AND r.created > f_report_rank_cap() in die Abfrage oben und beobachten Sie den Unterschied. Überprüfen Sie mit EXPLAIN ANALYZE, ob der Index verwendet wird .

Das Handbuch zu Parallelität und REINDEX :