PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Pobierz najnowsze dziecko na rodzica z dużej tabeli — zapytanie jest zbyt wolne

Najważniejszym punktem jest najprawdopodobniej to, że JOIN i GROUP nad wszystkim, aby uzyskać max(created) . Pobierz tę wartość osobno.

Wspomniałeś o wszystkich indeksach, które są potrzebne tutaj:na report_rank.created i na kluczach obcych. Dobrze sobie tam radzisz. (Jeśli interesuje Cię coś lepszego niż „w porządku”, czytaj dalej !)

LEFT JOIN report_site zostanie zmuszony do zwykłego JOIN przez WHERE klauzula. Zastąpiłem zwykły JOIN . Bardzo uprościłem też twoją składnię.

Zaktualizowano w lipcu 2015 r. z prostszymi, szybszymi zapytaniami i inteligentniejszymi funkcjami.

Rozwiązanie dla wielu rzędów

report_rank.created jest nie niepowtarzalny i chcesz wszystkie najnowsze wiersze.
Korzystanie z funkcji okna rank() w podzapytaniu.

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;

Dlaczego DESC NULLS LAST ?

Rozwiązanie dla jednego rzędu

Jeśli report_rank.created jest wyjątkowy lub jesteś zadowolony z dowolnego 1 wiersza z 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;

Powinno być jeszcze szybciej. Więcej opcji:

Najwyższa prędkość z dynamicznie dostosowywanym indeksem częściowym

Być może zauważyłeś skomentowaną część ostatniego zapytania:

AND  r.created > f_report_rank_cap()

Wspomniałeś o 50 mln. rzędy, to dużo. Oto sposób na przyspieszenie:

  • Utwórz prosty IMMUTABLE funkcja zwracająca znacznik czasu, który na pewno jest starszy niż wiersze zainteresowania, a jednocześnie jest tak młody, jak to możliwe.
  • Utwórz indeks częściowy tylko w młodszych wierszach - na podstawie tej funkcji.
  • Użyj WHERE warunek w zapytaniach, które pasują do warunku indeksu.
  • Utwórz inną funkcję, która zaktualizuje te obiekty do najnowszego wiersza za pomocą dynamicznego kodu DDL. (Musisz bezpieczny margines w przypadku, gdy najnowsze wiersze zostaną usunięte / dezaktywowane - jeśli to może się zdarzyć)
  • Wywołaj tę drugorzędną funkcję w czasie przerwy z minimalną równoczesną aktywnością na zadanie lub na żądanie. Tak często, jak chcesz, nie może zaszkodzić, wystarczy krótka, ekskluzywna blokada na stole.

Oto kompletne działające demo .
@erikcw, musisz aktywować komentowaną część zgodnie z poniższymi instrukcjami.

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

Zadzwoń:

SELECT f_report_rank_set_cap();

Zobacz:

SELECT f_report_rank_cap();

Odkomentuj klauzulę AND r.created > f_report_rank_cap() w powyższym zapytaniu i obserwuj różnicę. Sprawdź, czy indeks jest używany z EXPLAIN ANALYZE .

Podręcznik dotyczący współbieżności i REINDEX :



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. funkcja postgres unaccent vs transliteracja RoR

  2. Postgresql — twórz dynamicznie bazę danych i tabelę

  3. Dodawanie obiektów Postgres do Template1

  4. jak poprawnie określić schemat bazy danych w wiosennym rozruchu?

  5. Jak usunąć duplikaty, aby w tabeli istniały tylko pary?