PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Вземете най-новото дете на родител от голяма таблица - заявката е твърде бавна

Най-важното най-вероятно е да JOIN и GROUP над всичко само за да получите max(created) . Вземете тази стойност отделно.

Вие споменахте всички индекси, които са необходими тук:на report_rank.created и на външните ключове. Справяш се добре там. (Ако се интересувате от нещо по-добро от „добре“, продължете да четете !)

LEFT JOIN report_site ще бъде принуден към обикновен JOIN чрез WHERE клауза. Замених обикновен JOIN . Освен това много опростих вашия синтаксис.

Актуализирано през юли 2015 г. с по-прости, по-бързи заявки и по-интелигентни функции.

Решение за множество редове

report_rank.created не е уникален и искашсички най-новите редове.
Използване на функцията прозорец rank() в подзаявка.

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;

Защо DESC NULLS LAST ?

Решение за един ред

Ако report_rank.created е уникилна или сте доволни от всеки 1 ред с 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;

Все пак трябва да е по-бързо. Още опции:

Изключителна скорост с динамично коригиран частичен индекс

Може да сте забелязали коментираната част в последната заявка:

AND  r.created > f_report_rank_cap()

Споменахте 50 млн. редове, това е много. Ето начин да ускорите нещата:

  • Създайте прост IMMUTABLE функция, връщаща клеймо за време, което гарантирано е по-старо от интересните редове, като същевременно е възможно най-младо.
  • Създайте частичен индекс само на по-млади редове - въз основа на тази функция.
  • Използвайте WHERE условие в заявки, което съответства на условието на индекса.
  • Създайте друга функция, която актуализира тези обекти до последния ред с динамичен DDL. (Без сигурен марж в случай че най-новият(те) ред(ове) се изтрие/деактивира - ако това може да се случи)
  • Извиквайте тази вторична функция в извънработно време с минимална едновременна дейност на cronjob или при поискване. Колкото и често да искате, не може да навреди, просто се нуждае от кратко изключително заключване на масата.

Ето пълна работеща демонстрация .
@erikcw, ще трябва да активирате коментираната част, както е указано по-долу.

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

Обаждане:

SELECT f_report_rank_set_cap();

Вижте:

SELECT f_report_rank_cap();

Разкоментирайте клаузата AND r.created > f_report_rank_cap() в заявката по-горе и наблюдавайте разликата. Проверете дали индексът се използва с EXPLAIN ANALYZE .

Ръководството за едновременност и 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. PostgreSQL - Добавете ключ към всеки обект от JSONB масив

  2. Получаване на размера на множество таблици в една заявка POSTGRES?

  3. Как да потърся нулеви стойности в json поле тип postgresql?

  4. Вземете най-често срещаната стойност за всяка стойност на друга колона в SQL

  5. Формат на времето на Rails Activerecord/Postgres