Postgres: оптимизировать огромный GROUP BY - PullRequest
0 голосов
/ 10 января 2019

У меня есть такая таблица:

CREATE TABLE values (first_id varchar(26), sec_id int, mode varchar(6), external1_id varchar(23), external2_id varchar(26), x int, y int);

Может быть несколько значений, имеющих одинаковый first_id, моя цель состоит в том, чтобы свести (в json) для каждого first_id, всех связанных строк, в другую таблицу.

Я делаю так:

INSERT INTO othervalues(first_id, results)
  SELECT first_id, json_agg(values) AS results FROM values GROUP BY first_id;

В столбце результатов у меня есть массив всех строк json, который я могу использовать позже, как есть.

Моя проблема в том, что это очень медленно, с огромной таблицей: примерно 100 000 000 строк в значениях, это замедляет мой компьютер (я на самом деле тестирую локально), пока он не умрет (это Ubuntu).

Используя EXPLAIN я заметил, что используется GroupPartitioner, я добавил:

SET work_mem = '1GB';

Теперь он использует HashPartitioner, но это все равно убивает мой компьютер. Объяснение дает мне:

Insert on othervalues  (cost=2537311.89..2537316.89 rows=200 width=64)
  ->  Subquery Scan on "*SELECT*"  (cost=2537311.89..2537316.89 rows=200 width=64)
        ->  HashAggregate  (cost=2537311.89..2537314.39 rows=200 width=206)
              ->  Seq Scan on values  (cost=0.00..2251654.26 rows=57131526 width=206)

Есть идеи, как его оптимизировать?

1 Ответ

0 голосов
/ 14 января 2019

Решение, которое я в конечном итоге использую, состоит в том, чтобы разбить GROUP BY на несколько:

Сначала я создаю временную таблицу с уникальными идентификаторами материала, который я хочу сгруппировать. Это позволяет получить только часть результатов - как с OFFSET и LIMIT - но они могут быть очень медленными с огромными наборами результатов (большое смещение означает, что дерево выполнения покажет первые результаты):

CREATE TEMP TABLE tempids AS SELECT ROW_NUMBER() OVER (ORDER BY theid), theid FROM (SELECT DISTINCT theid FROM sourcetable) sourcetable;

Затем в цикле WHILE:

DO $$DECLARE
  r record;
  i INTEGER := 0;
  step INTEGER := 500000;
  size INTEGER := (SELECT COUNT(*) FROM tempids);
  BEGIN
  WHILE i <= size
      LOOP
        INSERT INTO target(theid, theresult)
        SELECT ... 
        WHERE tempids > i AND tempids < i + step
       GROUP BY tempids.theid;

Это похоже на обычное кодирование, это не хороший sql, но это работает.

...