sql >> Database teknologi >  >> RDS >> PostgreSQL

Hent seneste barn pr. forælder fra big table - forespørgslen er for langsom

Det vigtigste punkt er højst sandsynligt, at du JOIN og GROUP over alt bare for at få max(created) . Få denne værdi separat.

Du nævnte alle de indekser, der er nødvendige her:på report_rank.created og på fremmednøglerne. Du har det godt der. (Hvis du er interesseret i bedre end "okay", fortsæt med at læse !)

LEFT JOIN report_site vil blive tvunget til en almindelig JOIN ved WHERE klausul. Jeg erstattede en almindelig JOIN . Jeg har også forenklet din syntaks meget.

Opdateret juli 2015 med enklere, hurtigere forespørgsler og smartere funktioner.

Løsning til flere rækker

report_rank.created er ikke unik og du vil have alle de seneste rækker.
Brug af vinduesfunktionen rank() i en underforespørgsel.

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;

Hvorfor DESC NULLS LAST ?

Løsning for én række

Hvis report_rank.created er unik eller du er tilfreds med en hvilken som helst række med 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;

Det burde stadig være hurtigere. Flere muligheder:

Ultimativ hastighed med dynamisk justeret delvist indeks

Du har muligvis bemærket den kommenterede del i den sidste forespørgsel:

AND  r.created > f_report_rank_cap()

Du nævnte 50 mio. rækker, det er meget. Her er en måde at fremskynde tingene på:

  • Opret en simpel IMMUTABLE funktion, der returnerer et tidsstempel, der garanteret er ældre end rækker af interesse, mens det er så ungt som muligt.
  • Opret et delvist indeks kun på yngre rækker - baseret på denne funktion.
  • Brug en WHERE betingelse i forespørgsler, der matcher indeksbetingelsen.
  • Opret en anden funktion, der opdaterer disse objekter til den seneste række med dynamisk DDL. (Minus en sikker margen i tilfælde af at den eller de nyeste rækker bliver slettet / deaktiveret - hvis det kan ske)
  • Start denne sekundære funktion på pauser med et minimum af samtidig aktivitet pr. cronjob eller efter behov. Så ofte du vil, kan ikke gøre skade, den skal bare have en kort eksklusiv lås på bordet.

Her er en komplet fungerende demo .
@erikcw, du bliver nødt til at aktivere den kommenterede del som beskrevet nedenfor.

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

Ring til:

SELECT f_report_rank_set_cap();

Se:

SELECT f_report_rank_cap();

Fjern kommentarsætningen AND r.created > f_report_rank_cap() i forespørgslen ovenfor og observer forskellen. Bekræft, at indekset bliver brugt med EXPLAIN ANALYZE .

Manualen om samtidighed og REINDEX :



  1. Solve kan ikke udføre en DML-handling i en forespørgsel

  2. Sådan finder du navnet på en begrænsning i Oracle

  3. Hvordan man designer en rejsewebstedsdatabase

  4. MySQL:hvorfor varchar(254) og ikke varchar(255)?