Медленная транзакция SQL, получающая последние строки меток времени из таблицы в Postgres - PullRequest
0 голосов
/ 10 октября 2011

У меня проблемы с медленной транзакцией в Postgres, пытающейся получить последние цены по каталогу товаров, чья покупка больше, чем их продажа. На данный момент это довольно большая таблица, более 2 миллионов строк. У меня есть это в исторических целях. В настоящее время я использую:

select * from ta_price a
  join (
   select catalogproduct_id, max(timestamp) ts
     from ta_price
    group by catalogproduct_id
       ) b on a.catalogproduct_id = b.catalogproduct_id
          and a.timestamp = b.ts
          AND buy > sell;

catalogproduct_id - это внешний ключ для таблицы catalogproduct.

Из 2201760 строк, он выбирает 2296 строк. Общее время выполнения составляет 181 792,705 мс.

Есть идеи, как это улучшить?

Edit:

Я поражен всеми ответами! Я также хочу уточнить этот вопрос в рамках Django ORM. Я изо всех сил пытаюсь включить составной ключ (или подобное) в эту таблицу (используя catalogproduct_id и timestamp). У меня есть первичный ключ, который является автоинкрементным индексом, который, как мне кажется, так же хорош, как и его отсутствие.

Редактировать 2: После добавления частичного индекса, который предложил @Erwin, CREATE INDEX my_partial_idx ON ta_price (catalogproduct_id, timestamp) WHERE buy > sell;, я использую запрос от @wildplasser примерно на 10-12 секунд времени запроса. Для дальнейшего разъяснения в моей таблице приведены снимки цен (покупка и продажа) товаров с течением времени. В любой момент времени я хочу знать, какие продукты (на момент их последнего моментального снимка) имеют buy > sell.

Ответы [ 4 ]

2 голосов
/ 10 октября 2011

Пересмотренный ответ после некоторого рассмотрения

SELECT *
  FROM ta_price a
  JOIN (
   SELECT catalogproduct_id, max(timestamp) ts
     FROM ta_price
    GROUP BY catalogproduct_id
        ) b ON a.catalogproduct_id = b.catalogproduct_id
           AND a.timestamp = b.ts
           AND a.buy > a.sell;

buy и sell не квалифицированы в вашем вопросе.В зависимости от селективности buy > sell вы можете ускорить запрос, добавив ту же самую фразу WHERE в подвыбор.Однако это дает разные результаты .Я добавляю его на всякий случай, чтобы вы могли его пропустить:

SELECT *
  FROM ta_price a
  JOIN (
   SELECT catalogproduct_id, max(timestamp) ts
     FROM ta_price
    WHERE buy > sell
    GROUP BY catalogproduct_id
        ) b ON a.catalogproduct_id = b.catalogproduct_id
           AND a.timestamp = b.ts
 WHERE a.buy > a.sell;

В любом случае, простой индекс, такой как @Will, поможет:
CREATE INDEX my_idx ON ta_price (catalogproduct_id, отметка времени);

Однако существует более высокий подход.
Безусловный max() в подвыборке приведет к последовательному сканированию таблицы независимо от индексов.Такая операция никогда не будет быстрой с 2,2 м строк.
Условие JOIN в сочетании с предложением WHERE внешнего SELECT получит прибыль от индекса, подобного приведенному выше.В зависимости от селективности buy > sell a частичный индекс будет немного или существенно быстрее и, соответственно, меньше на диске и в ОЗУ:

CREATE INDEX my_partial_idx ON ta_price (catalogproduct_id, timestamp)
 WHERE buy > sell;

Порядок столбцов виндекс не имеет значения в этом случае.Это также ускорит мой второй вариант запроса.

Вы упомянули таблицу для "исторических" целей?Если это означает, что нет новых данных, вы могли бы значительно ускорить процесс с помощью материализованного представления .


С другой стороны: я бы не использовал timestamp в качестве имени столбца.Это разрешено в PostgreSQL, но это зарезервированное слово во всех стандартах SQL.


ОК, первые вещи последние : для таблицы из 2,2 м строквам нужно намного больше ресурсов, чем есть в Postgres.

  • Посмотрите на ваш файл postgresql.conf и проверьте настройки для shared_buffers и work_mem для начала.
  • Обратитесь к postgres wiki для настройки производительности
  • Обратитесь к руководству по штрафам по Расход ресурсов
  • Обратитесь к руководству по штрафам по Расходам на планировщик
  • Увеличьте этинастройка статистики:
    ALTER TABLE tmp.ta_price ALTER COLUMN buy SET STATISTICS 1000;<br> ALTER TABLE tmp.ta_price ALTER COLUMN sell SET STATISTICS 1000;<br> ALTER TABLE tmp.ta_price ALTER COLUMN ts SET STATISTICS 1000;

    Затем запустите ANALYZE tmp.ta_price;

  • Убедитесь, что autovacuum работает.Если сомневаетесь, запустите VACUUM ANALYZE ta_price и посмотрите, имел ли он эффект.


Я играл с тестовой настройкой wildplasser (, которая была очень полезной! ) на установке pg 8.4 с ограниченными ресурсами.Вот общее время выполнения с EXPLAIN ANYLYZE

Erwin 1)        901.487 ms  
wildplasser 1) 1148.045 ms  
A.H.           2922.113 ms  

Вариант 2 с дополнительным предложением (покупка> продажа):

Erwin 2)        536.678 ms  
wildplasser 2)  809.215 ms  

С частичным индексом:

Erwin 1)       1166.793 ms  -- slower (!), than unexpected

Вероятно, расходы на планировщик выключены, этот тестовый кластер базы данных оптимизирован для основной базы данных, которая имеет гораздо больше ресурсов.

wildplasser 1) 1122.609 ms -- rest is faster as expected  

Erwin 2)        481.487 ms  
wildplasser 2)  769.887 ms  

Резюме

Версия AH занимает намного больше времени (тот же результат, о котором вы сообщали).Оконные функции имеют тенденцию быть медленными, особенно в старых версиях postgres.Мой альтернативный запрос в два раза быстрее, чем ожидалось.Вопрос в том, нужны ли разные результаты - может быть, нет.

В любом случае, это были 300 тыс. СтрокЗапрос занимает 0,5 - 1 с в версии 8.4 с ограниченными ресурсами (но в основном с правильными настройками) на 5-летнем сервере.С приличной машиной и приличными настройками (достаточно оперативной памяти!) Вы должны понизить ее до при 10 с как минимум.

2 голосов
/ 10 октября 2011
SELECT * from ta_price a
WHERE NOT EXISTS (
  SELECT *
  FROM ta_price b
  WHERE b.catalogproduct_id = a.catalogproduct_id
    AND b.timestamp > a.timestamp
    -- AND b.buy > b.sell -- Not clear if OP wants this
    )
AND a.buy > a.sell
;

Индекс на (catalogproduct_id, timestamp), вероятно, полезен. Возможно, понадобится дополнительное условие «AND b.buy> b.sell» в подзапросе (в тексте OP не ясно, что действительно нужно).

ОБНОВЛЕНИЕ: отметка времени является зарезервированным словом. Я немного изменил это. Также добавлены тестданные.

DROP SCHEMA tmp cascade;
CREATE SCHEMA tmp ;

CREATE TABLE tmp.ta_price
    ( catalogproduct_id INTEGER NOT NULL
    , tttimestamp timestamp NOT NULL
    , buy DECIMAL (10,2)
    , sell DECIMAL (10,2)
    );
INSERT INTO tmp.ta_price(catalogproduct_id,tttimestamp,buy,sell)
    SELECT serie_n
    , serie_t
    , serie_v + ((100* random()) - 30)
    , serie_v + ((100* random()) - 20)
    FROM generate_series (1,10000) serie_n
    , generate_series ( '2011-09-01 00:00:00' , '2011-10-01 00:00:00' , '1 day' ::interval) serie_t
    , generate_series ( 100 , 100 ) serie_v
    ;
DELETE FROM tmp.ta_price WHERE random() < 0.02;

CREATE INDEX tmptmp ON tmp.ta_price (catalogproduct_id,tttimestamp);

-- there may be some duplicate records: clear them
DELETE FROM tmp.ta_price a
WHERE EXISTS (SELECT * FROM tmp.ta_price b
    WHERE b.catalogproduct_id = a.catalogproduct_id
    AND b.tttimestamp = a.tttimestamp
    AND b.ctid > a.ctid
    );

DROP INDEX tmp.tmptmp ;

ALTER TABLE tmp.ta_price
    ADD PRIMARY KEY (catalogproduct_id,tttimestamp)
    ;

EXPLAIN ANALYZE
SELECT * from tmp.ta_price a
WHERE NOT EXISTS (
  SELECT *
  FROM tmp.ta_price b
  WHERE b.catalogproduct_id = a.catalogproduct_id
    AND b.tttimestamp > a.tttimestamp
    -- AND b.buy > b.sell -- Not clear if OP wants this
    )
AND a.buy > a.sell
;

Queryplan: (для записей 300K в ta_price)

  ------------------------------------------------------------------------------
 Nested Loop Anti Join  (cost=0.00..8607.82 rows=67508 width=38) (actual time=457.486..482.943 rows=4052 loops=1)
   ->  Seq Scan on ta_price a  (cost=0.00..6381.34 rows=101262 width=38) (actual time=0.027..80.256 rows=123142 loops=1)
         Filter: (buy > sell)
   ->  Index Scan using ta_price_pkey on ta_price b  (cost=0.00..10.57 rows=506 width=12) (actual time=0.003..0.003 rows=1 loops=123142)
         Index Cond: ((b.catalogproduct_id = a.catalogproduct_id) AND (b.tttimestamp > a.tttimestamp))
 Total runtime: 483.325 ms
(6 rows)
0 голосов
/ 10 октября 2011

Ваш запрос присоединит таблицу к себе, чтобы получить максимум.Вы можете попытаться " Window Functions " предотвратить это - возможно, они лучше работают в вашем случае:

SELECT * FROM (
    SELECT *, rank() OVER w
    FROM ta_price                             
    WINDOW w AS (PARTITION BY catalogproduct_id ORDER BY timestamp DESC)
) c WHERE c.rank = 1 AND c.buy > c.sell;
0 голосов
/ 10 октября 2011

Я бы попытался добавить индекс для catalogproduct_id и timestamp.Похоже, это сканирование таблицы без дополнительной информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...