Полнотекстовые запросы к этой базе данных (хранение билетов RT ( Request Tracker )) выполняются очень долго. Таблица вложений (содержащая полнотекстовые данные) составляет около 15 ГБ.
Схема базы данных выглядит следующим образом, это около 2 миллионов строк:
rt4 = # \ d + вложения Таблица "public.attachments" Колонка | Тип | Модификаторы | Хранение Описание ----------------- + ----------------------------- + - -------------------------------------------------- ------ + ---------- + ------------- id | целое число | notval по умолчанию nextval ('attachments_id_seq' :: regclass) | равнина | транзакции | целое число | не нуль | равнина | родитель | целое число | не нуль по умолчанию 0 | равнина | MessageID | изменение характера (160) | | расширенный | предмет | изменение характера (255) | | расширенный | имя файла | изменение характера (255) | | расширенный | тип контента | изменение характера (80) | | расширенный | кодирование контента | изменение характера (80) | | расширенный | содержание | текст | | расширенный | заголовки | текст | | расширенный | создатель | целое число | не нуль по умолчанию 0 | равнина | создано | отметка времени без часового пояса | | равнина | contentindex | цветок | | расширенный | Индексы: "attachments_pkey" ПЕРВИЧНЫЙ КЛЮЧ, btree (id) "attachments1" btree (родитель) "attachments2" btree (транзакция) "attachments3" btree (родитель, транзакция) "contentindex_idx" джин (contentindex) Имеет OIDs: нет
Я могу быстро запросить базу данных (<1 с) с помощью запроса, такого как:
select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');
Однако, когда RT запускает запрос, который должен выполнить поиск по полнотекстовому индексу по той же таблице, обычно требуется сотни секунд. Результат анализа запроса выглядит следующим образом:
запрос
SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);
EXPLAIN ANALYZE
вывод
QUERY PLAN -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------- Агрегат (стоимость = 51210.60..51210.61 строк = 1 ширина = 4) (фактическое время = 477778.806..477778.806 строк = 1 цикл = 1) -> Вложенный цикл (стоимость = 0,00..51210.57 строк = 15 по ширине = 4) (фактическое время = 17943.986..477775.174 строк = 4197 циклов = 1) -> Вложенный цикл (стоимость = 0.00..40643.08 строк = 6507 ширина = 8) (фактическое время = 8.526..20610.380 строк = 1714818 циклов = 1) -> Seq Scan на основном тикете (стоимость = 0.00..9818.37 строк = 598 ширины = 8) (фактическое время = 0.008..256.042 строк = 96990 циклов = 1) Фильтр: ((((статус) :: текст «удален» :: текст) И (id =ffectiveid) И ((тип) :: текст = «билет» :: текст)) -> Сканирование индекса с использованием транзакций1 для транзакций транзакций_1 (стоимость = 0,00..51.36 строк = 15 ширины = 8) (фактическое время = 0.102..0.202 строк = 18 циклов = 96990) Индекс Cond: (((тип объекта) :: text = 'RT :: Ticket' :: text) AND (objectid = main.id)) -> Сканирование индекса с использованием вложений2 на вложениях вложения_2 (стоимость = 0,00..1,61 строк = 1 ширина = 4) (фактическое время = 0,266..0.266 строк = 0 циклов = 1714818) Индекс Cond: (транзакция = транзакции_1.id) Фильтр: (contentindex @@ plainto_tsquery ('frobnicate' :: text)) Общее время выполнения: 477778,883 мс
Насколько я могу судить, проблема заключается в том, что он не использует индекс, созданный для contentindex
field ( contentindex_idx
), а выполняет фильтр для большого количества совпадающих строк в таблице вложений. Число строк в выходных данных объяснения также представляется крайне неточным, даже после недавнего ANALYZE
: оценочные строки = 6507 фактических строк = 1714818.
Я не совсем уверен, куда идти дальше с этим.
Ответы:
Это может быть улучшено тысячей и одним способом, тогда это должно быть делом миллисекунд .
Лучшие запросы
Это всего лишь ваш запрос, переформатированный с использованием псевдонимов и удаленного шума, чтобы очистить туман:
Большая часть проблемы с вашим запросом лежит в первых двух таблицах
tickets
иtransactions
, которые отсутствуют в вопросе. Я заполняюсь образованными догадками.t.status
,t.objecttype
Иtr.objecttype
, вероятно , не будетtext
, ноenum
и , возможно , некоторые очень малое значение ссылки на справочную таблицу.EXISTS
ПолусоединениеПредполагая,
tickets.id
что это первичный ключ, эта переписанная форма должна быть намного дешевле:Вместо умножения строк на два объединения 1: n, только для свертывания нескольких совпадений в конце
count(DISTINCT id)
, используйтеEXISTS
полусоединение, которое может перестать искать дальше, как только будет найдено первое совпадение, и в то же время устареет последнийDISTINCT
шаг. По документации:Эффективность зависит от количества транзакций на тикет и вложений на транзакцию.
Определить порядок соединений с
join_collapse_limit
Если вы знаете, что ваш поисковый термин для
attachments.contentindex
является очень избирательным - более избирательным, чем другие условия в запросе (что, вероятно, имеет место для «frobnicate», но не для «проблемы»), вы можете форсировать последовательность соединений. Планировщик запросов едва ли может судить об избирательности отдельных слов, кроме самых распространенных. По документации:Используйте
SET LOCAL
с целью установить его только для текущей транзакции.Порядок
WHERE
условий всегда не имеет значения. Здесь важен только порядок соединений.Или используйте CTE, как объясняет @jjanes в «Варианте 2». для аналогичного эффекта.
Индексы
B-дерево индексов
Возьмите все условия
tickets
, которые используются одинаково с большинством запросов, и создайте частичный индекс дляtickets
:Если одно из условий является переменным, удалите его из
WHERE
условия и вместо этого добавьте столбец как столбец индекса.Еще один на
transactions
:Третий столбец предназначен только для сканирования по индексу.
Кроме того, поскольку у вас есть этот составной индекс с двумя целочисленными столбцами
attachments
:Этот дополнительный индекс является пустой тратой , удалите его:
Подробности:
Индекс GIN
Добавьте
transactionid
в свой индекс GIN, чтобы сделать его намного более эффективным. Это может быть еще одна серебряная пуля , потому что она потенциально позволяет сканировать только по индексу, полностью исключая посещение большого стола.Вам нужны дополнительные классы операторов, предоставляемые дополнительным модулем
btree_gin
. Подробные инструкции:4 байта из
integer
столбца не делают индекс намного больше. Кроме того, к счастью для вас, индексы GIN отличаются от индексов B-дерева в решающем аспекте. По документации:Жирный акцент мой. Так что вам просто нужен один (большой и несколько дорогой) индекс GIN.
Определение таблицы
Переместить
integer not null columns
на передний план. Это имеет несколько небольших положительных эффектов на хранение и производительность. В этом случае экономит 4 - 8 байт на строку.источник
Опция 1
Планировщик не имеет представления об истинной природе отношений между EffectiveId и id, и поэтому, вероятно, считает, что предложение:
будет гораздо более избирательным, чем на самом деле. Если это то, что я думаю, EffectiveID почти всегда равен main.id, но планировщик этого не знает.
Возможно, лучший способ сохранить этот тип отношений, как правило, состоит в том, чтобы определить значение NULL для EffectiveID, чтобы оно означало «фактически то же самое, что и идентификатор», и сохранить в нем что-то, только если есть разница.
Предполагая, что вы не хотите реорганизовывать свою схему, вы можете попытаться обойти ее, переписав это предложение примерно так:
Планировщик может предположить, что
between
он менее избирателен, чем равенство, и этого может быть достаточно, чтобы вывести его из существующей ловушки.Вариант 2
Другой подход заключается в использовании CTE:
Это заставляет планировщика использовать ContentIndex в качестве источника избирательности. Как только это будет сделано, вводящие в заблуждение корреляции столбцов в таблице «Билеты» больше не будут выглядеть так привлекательно. Конечно, если кто-то ищет «проблему», а не «фробникат», это может иметь неприятные последствия.
Вариант 3
Для дальнейшего изучения неверных оценок строк необходимо выполнить приведенный ниже запрос во всех 2 ^ 3 = 8 перестановках различных предложений AND, которые закомментированы. Это поможет выяснить, откуда исходит плохая оценка.
источник