PostgreSQL медленный COUNT () - является ли триггер единственным решением? - PullRequest
1 голос
/ 11 апреля 2011

У меня есть таблица с сообщениями, которые классифицируются по:

  • type
  • tag
  • language

Всеэти «категории» хранятся в следующих таблицах (posts_types) и связаны через следующие таблицы (posts_types_assignment).

УЧЕТ в PostgreSQL действительно медленный (у меня более 500k записей в этой таблице)и мне нужно получить количество постов, классифицированных по любой комбинации типа / тега / языка.

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

Есть ли другое решение, как эффективно получить фактическое количество постов, классифицированных в любом типе / теге / языке?

Ответы [ 2 ]

1 голос
/ 19 апреля 2011

Как отмечает btilly, и если он правильно угадал схему, дизайн таблицы не помогает - кажется (на первый взгляд, по крайней мере), что, например, три таблицы posts_tag(post_id,tag) post_lang(post_id,lang) post_type(post_id,type) будет более естественным и гораздо более эффективным.

Помимо этого (или в дополнение к этому), можно подумать о таблице или материализованном представлении, которое суммирует все возможные подсчеты со столбцами (lang,type,tag,nposts).Конечно, вычислить это полностью ОЧЕНЬ медленно, но (кроме первого раза) это можно сделать либо полностью «в фоновом режиме», через определенные промежутки времени (если данные не сильно различаются, и если вы нене требует точного подсчета), или охотно с триггерами.См. Например здесь

1 голос
/ 16 апреля 2011

Позвольте мне получить это прямо.

У вас есть стол posts. У вас есть стол posts_types. У этих двух есть много ко многим, чтобы присоединиться к posts_types_assignment. И у вас есть такой медленный запрос:

SELECT count(*)
FROM posts p
  JOIN posts_types_assigment pta1
    ON p.id = pta1.post_id
  JOIN posts_types pt1
    ON pt1.id = pta1.post_type_id
      AND pt1.type = 'language'
      AND pt1.name = 'English'
  JOIN posts_types_assigment pta2
    ON p.id = pta2.post_id
  JOIN posts_types pt2
    ON pt2.id = pta2.post_type_id
      AND pt2.type = 'tag'
      AND pt2.name = 'awesome'

И вы хотели бы знать, почему это мучительно медленно.

Мое первое замечание: PostgreSQL должен был бы выполнять намного меньше работы, если бы у вас были идентификаторы в таблице posts, а не в соединениях. Но это спорный вопрос, решение принято.

Мое более полезное замечание - я считаю, что PostgreSQL имеет оптимизатор запросов, аналогичный Oracle. В этом случае, чтобы ограничить комбинаторный рост возможных планов запросов, которые он должен учитывать, он рассматривает только планы, начинающиеся с некоторой таблицы, а затем многократно объединяет еще один набор данных за раз. Однако такой план запроса не будет работать здесь. Вы можете начать с pt1, получить 1 запись, затем перейти к pta1, получить набор записей, присоединиться к p, получить такое же количество записей, затем присоединиться к pta2, и теперь вы получите огромное количество записей, затем присоединитесь к pt2, получите всего несколько записей. Присоединение к pta2 - это медленный шаг, потому что база данных не знает, какие записи вам нужны, и поэтому должна создать временный набор результатов для каждой комбинации сообщения и фрагмента метаданных (типа, языка или тега) на нем .

Если это действительно ваша проблема, то план right выглядит следующим образом. Присоединяйтесь pt1 к pta1, добавьте индекс. Присоедините pt2 к pta2, затем присоединитесь к результату первого запроса, затем присоединитесь к p. Тогда посчитай. Это означает, что мы не получаем огромных наборов результатов.

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

CREATE TEMPORARY TABLE t1
AS
SELECT pta*
FROM posts_types pt
  JOIN posts_types_assignment pta
    ON pt.id = pta.post_type_id
WHERE pt.type = 'language'
  AND pt.name = 'English';

CREATE INDEX idx1 ON t1 (post_id);

CREATE TEMPORARY TABLE t2
AS
SELECT pta*
FROM posts_types pt
  JOIN posts_types_assignment pta
    ON pt.id = pta.post_type_id
  JOIN t1
    ON t1.post_id = pta.post_id
WHERE pt.type = 'language'
  AND pt.name = 'English';

SELECT COUNT(*)
FROM posts p
  JOIN t1
    ON p.id = t1.post_id;

Запрет случайных опечаток и т. Д., Скорее всего, будет лучше. Если это не так, дважды проверьте индексы в своих таблицах.

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