sql >> Databáze >  >> RDS >> PostgreSQL

Získejte nejnovější dítě na rodiče z velké tabulky – dotaz je příliš pomalý

Hlavním bodem je s největší pravděpodobností, že se JOIN a GROUP nad vším, jen abyste získali max(created) . Získejte tuto hodnotu samostatně.

Všechny indexy, které jsou potřeba, jste zmínili zde:na report_rank.created a na cizí klíče. Máš se tam dobře. (Pokud vás zajímá něco lepšího než „v pořádku“, čtěte dál !)

LEFT JOIN report_site bude vynuceno k prostému JOIN pomocí WHERE doložka. Nahradil jsem prosté JOIN . Také jsem hodně zjednodušil vaši syntaxi.

Aktualizováno v červenci 2015 s jednoduššími, rychlejšími dotazy a chytřejšími funkcemi.

Řešení pro více řádků

report_rank.created není jedinečný a chcete vše nejnovější řádky.
Pomocí funkce okna rank() v dílčím dotazu.

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;

Proč DESC NULLS LAST ?

Řešení pro jeden řádek

Pokud report_rank.created je jedinečný nebo jste spokojeni s jakýmkoli 1 řádkem s 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;

Ještě by to mělo být rychlejší. Další možnosti:

Maximální rychlost s dynamicky upraveným dílčím indexem

Možná jste si všimli komentované části v posledním dotazu:

AND  r.created > f_report_rank_cap()

Zmínil jste 50 mil. řádky, to je hodně. Zde je způsob, jak věci urychlit:

  • Vytvořte jednoduchý IMMUTABLE funkce vracející časové razítko, které je zaručeně starší než řádky zájmu, a přitom je co nejmladší.
  • Vytvořte částečný index pouze na mladších řádcích – na základě této funkce.
  • Použijte WHERE podmínku v dotazech, které odpovídají podmínce indexu.
  • Vytvořte další funkci, která aktualizuje tyto objekty na nejnovější řádek pomocí dynamického jazyka DDL. (Mínus zabezpečená marže v případě, že budou nejnovější řádky smazány / deaktivovány – pokud se to může stát)
  • Vyvolejte tuto sekundární funkci ve vypnutých dobách s minimem souběžné aktivity na cronjob nebo na vyžádání. Jak často chcete, nemůže ublížit, potřebuje pouze krátký exkluzivní zámek na stole.

Zde je kompletní funkční demo .
@erikcw, budete muset aktivovat komentovanou část podle pokynů níže.

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

Volejte:

SELECT f_report_rank_set_cap();

Viz:

SELECT f_report_rank_cap();

Odkomentujte klauzuli AND r.created > f_report_rank_cap() v dotazu výše a pozorujte rozdíl. Ověřte, že se index používá pomocí EXPLAIN ANALYZE .

Příručka pro souběžnost a REINDEX :



  1. Použití file_get_html(); vrací HTML se speciálními znaky, i když používám -> prostý text

  2. Funkce LEAST() v PostgreSQL

  3. Jak zvýšit pole v MySql pomocí ON DUPLICATE KEY UPDATE při vkládání více řádků?

  4. Zamykání a souběžné provádění uložené procedury