Postgres Извлечение JSONB происходит очень медленно - PullRequest
1 голос
/ 06 августа 2020

Здесь немного проигрыш. Прежде всего, я не dba, и у меня нет опыта работы с Postgres, за исключением того, что я делаю сейчас.

Postgres кажется задыхается, когда вы хотите вернуть jsonb документы вообще на что-нибудь больше пары сотен строк. Когда вы пытаетесь вернуть тысячи, производительность запроса становится ужасной. Если вы go еще дальше и попытаетесь вернуть несколько документов jsonb после различных объединений таблиц, забудьте об этом.

Вот мой сценарий с некоторым кодом:

У меня есть 3 таблицы - все таблицы имеют jsonb, все сложные модели и 2 из которых имеют размер (от 8 до 12 КБ без сжатия). В этой конкретной операции мне нужно разложить массив элементов jsonb, чтобы затем проработать его - это дает мне примерно 12k записей.

Каждая запись затем содержит идентификатор, который я использую для присоединения к другой важной таблице - мне нужно получить jsonb do c из этой таблицы. Оттуда мне нужно присоединить эту таблицу к другой (гораздо меньшей) таблице, а также вытащить оттуда do c на основе другого ключа.

Таким образом, на выходе получается несколько столбцов + 3 документа jsonb в диапазоне от Размер от <1 КБ до примерно 12 КБ без сжатия. </p>

Получение данных запроса фактически бессмысленно - я еще не видел данных, возвращаемых запросом. Как только я убираю столбцы json do c, естественно, скорость запроса увеличивается до секунд или меньше. 1 документ jsonb увеличивает время извлечения до 40 секунд в моем случае, добавление второй занимает у нас 2 минуты, а добавление третьего намного дольше.

Что я делаю не так? Есть ли способ получить документы jsonb эффективным способом?

SELECT x.id,
    a.doc1,
    b.doc2,
    c.doc3
   FROM ( SELECT id,
            (elements.elem ->> 'a'::text)::integer AS a,
            (elements.elem ->> 'b'::text)::integer AS b,
            (elements.elem ->> 'c'::text)::integer AS c,
            (elements.elem ->> 'd'::text)::integer AS d,
            (elements.elem ->> 'e'::text)::integer AS e
           FROM tab
             CROSS JOIN LATERAL jsonb_array_elements(tab.doc -> 'arrayList'::text) WITH ORDINALITY elements(elem, ordinality)) x
     LEFT JOIN table2 a ON x.id = a.id
     LEFT JOIN table3 b ON a.other_id = b.id
     LEFT JOIN table4 c ON b.other_id = c.id;

Сами таблицы довольно стандартные:

CREATE TABLE a ( 
  id (primary key), 
  other_id (foreign key), 
  doc jsonb 
)

Ничего особенного в этих таблицах, это идентификаторы и jsonb документы

Примечание - мы используем Postgres по нескольким причинам, нам действительно нужны реляционные аспекты PG, но в то же время нам нужно документировать возможность хранения и извлечения документов для дальнейшего использования в нашем рабочем процессе.

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

EDIT: добавлено объяснение:

                                                    QUERY PLAN
------------------------------------------------------------------------------------------------------------------
 Hash Left Join  (cost=465.79..96225.93 rows=11300 width=1843)
   Hash Cond: (pr.table_3_id = br.id)
   ->  Hash Left Join  (cost=451.25..95756.86 rows=11300 width=1149)
         Hash Cond: (((p.doc ->> 'secondary_id'::text))::integer = pr.id)
         ->  Nested Loop Left Join  (cost=0.44..95272.14 rows=11300 width=1029)
               ->  Nested Loop  (cost=0.01..239.13 rows=11300 width=40)
                     ->  Seq Scan on table_3  (cost=0.00..13.13 rows=113 width=710)
                     ->  Function Scan on jsonb_array_elements elements  (cost=0.01..1.00 rows=100 width=32)
               ->  Index Scan using table_1_pkey on table_1 p  (cost=0.43..8.41 rows=1 width=993)
                     Index Cond: (((elements.elem ->> 'field_id'::text))::integer = id)
         ->  Hash  (cost=325.36..325.36 rows=10036 width=124)
               ->  Seq Scan on table_2 pr  (cost=0.00..325.36 rows=10036 width=124)
   ->  Hash  (cost=13.13..13.13 rows=113 width=710)
         ->  Seq Scan on table_3 br  (cost=0.00..13.13 rows=113 width=710)
(14 rows)

EDIT2: Извините, было мега занято - я постараюсь более подробно рассказать о go - во-первых, полностью объяснить план (я не знал о дополнительных параметрах) - оставлю в реальных таблицах (я не был уверен, разрешено ли мне):

Hash Left Join  (cost=465.79..96225.93 rows=11300 width=1726) (actual time=4.669..278.781 rows=12522 loops=1)
   Hash Cond: (pr.brand_id = br.id)
   Buffers: shared hit=64813
   ->  Hash Left Join  (cost=451.25..95756.86 rows=11300 width=1032) (actual time=4.537..265.749 rows=12522 loops=1)
         Hash Cond: (((p.doc ->> 'productId'::text))::integer = pr.id)
         Buffers: shared hit=64801
         ->  Nested Loop Left Join  (cost=0.44..95272.14 rows=11300 width=912) (actual time=0.240..39.480 rows=12522 loops=1)
               Buffers: shared hit=49964
               ->  Nested Loop  (cost=0.01..239.13 rows=11300 width=40) (actual time=0.230..8.177 rows=12522 loops=1)
                     Buffers: shared hit=163
                     ->  Seq Scan on brand  (cost=0.00..13.13 rows=113 width=710) (actual time=0.003..0.038 rows=113 loops=1)
                           Buffers: shared hit=12
                     ->  Function Scan on jsonb_array_elements elements  (cost=0.01..1.00 rows=100 width=32) (actual time=0.045..0.057 rows=111 loops=113)
                           Buffers: shared hit=151
               ->  Index Scan using product_variant_pkey on product_variant p  (cost=0.43..8.41 rows=1 width=876) (actual time=0.002..0.002 rows=1 loops=12522)
                     Index Cond: (((elements.elem ->> 'productVariantId'::text))::integer = id)
                     Buffers: shared hit=49801
         ->  Hash  (cost=325.36..325.36 rows=10036 width=124) (actual time=4.174..4.174 rows=10036 loops=1)
               Buckets: 16384  Batches: 1  Memory Usage: 1684kB
               Buffers: shared hit=225
               ->  Seq Scan on product pr  (cost=0.00..325.36 rows=10036 width=124) (actual time=0.003..1.836 rows=10036 loops=1)
                     Buffers: shared hit=225
   ->  Hash  (cost=13.13..13.13 rows=113 width=710) (actual time=0.114..0.114 rows=113 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 90kB
         Buffers: shared hit=12
         ->  Seq Scan on brand br  (cost=0.00..13.13 rows=113 width=710) (actual time=0.003..0.043 rows=113 loops=1)
               Buffers: shared hit=12
 Planning Time: 0.731 ms
 Execution Time: 279.952 ms
(29 rows)

1 Ответ

0 голосов
/ 09 августа 2020

Ваш запрос сложно выполнить по нескольким причинам:

  1. Ваши таблицы называются tab, table2, table3, `table4.
  2. Ваш подзапрос анализирует JSON для каждой отдельной строки в таблице, проецирует некоторые значения, а затем внешний запрос никогда не использует эти значения. Единственное, что кажется релевантным, - это id.
  3. Внешние соединения должны выполняться по порядку, в то время как внутренние соединения можно свободно переупорядочивать для повышения производительности. Не зная цели этого запроса, я не могу определить, подходит ли внешнее соединение или нет.
  4. Имена таблиц и имена столбцов в плане выполнения не соответствуют запросу, поэтому я не убедился, что этот план точен.
  5. Вы не предоставили схему.

Тем не менее, я сделаю все возможное.

Вещи, которые выделяются производительностью- мудрый

Нет where предложение

Поскольку нет предложения where, ваш запрос будет запускать jsonb_array_elements для каждой отдельной строки tab, что и происходит. Помимо извлечения данных из JSON и сохранения их в отдельном столбце, я не могу себе представить, что можно было бы сделать для их оптимизации.

Недостаточно indexes для работы соединений

План запроса предполагает значительную стоимость объединений. За исключением table1, каждое соединение управляется последовательным сканированием таблиц, что означает чтение каждой строки обеих таблиц. Я подозреваю, что добавление индексов в каждую таблицу поможет. Похоже, вы присоединяетесь к id столбцам, поэтому простое ограничение primary key улучшит как целостность ваших данных, так и производительность запросов.

alter table tab add constraint primary key (id);
alter table table2 add constraint primary key (id);
alter table table3 add constraint primary key (id);
alter table table4 add constraint primary key (id);

Преобразование типов

Эта часть выполнения plan показывает преобразование двойного типа в вашем первом объединении:

Index Cond: (((elements.elem ->> 'field_id'::text))::integer = id)

Этот предикат означает, что значение id из tab преобразуется в текст, а затем текст преобразуется в целое число, чтобы оно могло соответствовать против table2.id. Эти преобразования могут быть дорогостоящими по времени вычислений и в некоторых случаях могут предотвратить использование индекса. Трудно посоветовать, что делать, потому что я не знаю, какие именно типы.

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