Задавая этот вопрос, специально для Postgres, так как он имеет хорошую поддержку для R-дерева / пространственных индексов.
У нас есть следующая таблица с древовидной структурой (модель Nested Set) слов и их частотами:
lexikon
-------
_id integer PRIMARY KEY
word text
frequency integer
lset integer UNIQUE KEY
rset integer UNIQUE KEY
И запрос:
SELECT word
FROM lexikon
WHERE lset BETWEEN @Low AND @High
ORDER BY frequency DESC
LIMIT @N
Я полагаю, что индекс покрытия (lset, frequency, word)
был бы полезен, но я чувствую, что он может работать неэффективно, если lset
в (@High, @Low)
диапазоне слишком много значений .
Простой индекс (frequency DESC)
может также быть достаточным иногда, когда поиск, использующий этот индекс, возвращает ранние @N
строки, которые соответствуют условию диапазона.
Но, похоже, производительность сильно зависит от значений параметров.
Есть ли способ заставить его работать быстро, независимо от того, (@Low, @High)
является ли диапазон широким или узким, и независимо от того, находятся ли слова с высокой частотой, к счастью, в (узком) выбранном диапазоне?
Поможет ли R-дерево / пространственный индекс?
Добавление индексов, переписывание запроса, перепроектирование таблицы, ограничений нет.
источник
lset,rset
иword
.Ответы:
Вы можете достичь лучшей производительности, выполняя поиск сначала в строках с более высокими частотами Это может быть достигнуто путем «гранулирования» частот и последующего пошагового их прохождения, например, следующим образом:
- тестовые и
lexikon
фиктивные данные:granule
анализ (в основном для информации и настройки):функция для сканирования высоких частот в первую очередь:
результаты (время может быть взято с небольшим количеством соли, но каждый запрос выполняется дважды, чтобы противостоять кешированию)
сначала используя функцию, которую мы написали:
а затем с помощью простого сканирования индекса:
В зависимости от ваших реальных данных, вы, вероятно, захотите изменить количество гранул и функцию, используемую для помещения в них строк. Ключевым здесь является фактическое распределение частот, а также ожидаемые значения для
limit
предложения и размерlset
искомых диапазонов.источник
width_granule=8
междуgranulae_start
иgranulae_end
предыдущего уровня?frequency
генерируется: большой разрыв между 1e6 / 2 и 1e6 / 3, чем больше число строк, тем меньше разрыв. В любом случае, спасибо за этот потрясающий подход!Настроить
Я основываюсь на настройке @ Jack, чтобы людям было легче следить и сравнивать. Протестировано с PostgreSQL 9.1.4 .
С этого момента я выбираю другой маршрут:
Вспомогательный стол
Это решение не добавляет столбцы в исходную таблицу, ему просто нужна крошечная вспомогательная таблица. Я поместил его в схему
public
, используйте любую схему по вашему выбору.Таблица выглядит так:
Поскольку этот столбец
cond
будет использоваться в динамическом SQL ниже, вам необходимо обеспечить безопасность этой таблицы . Всегда проверяйте схему таблицы, если вы не можете быть уверены в соответствующем токеsearch_path
, и отзывайте права на запись изpublic
(и любой другой ненадежной роли):Стол
lex_freq
служит трем целям:Индексы
Этот
DO
оператор создает все необходимые индексы:Все эти частичные индексы вместе охватывают таблицу один раз. Они имеют примерно одинаковый размер с одним основным индексом на всю таблицу:
Пока только 21 МБ индексов для таблицы 50 МБ.
Я создаю большинство частичных индексов
(lset, frequency DESC)
. Второй столбец помогает только в особых случаях. Но поскольку оба задействованных столбца имеют типinteger
, из-за особенностей выравнивания данных в сочетании с MAXALIGN в PostgreSQL, второй столбец не делает индекс больше. Это маленькая победа едва ли любой ценой.Нет смысла делать это для частичных индексов, которые охватывают только одну частоту. Это только на
(lset)
. Созданные индексы выглядят так:функция
Функция несколько похожа по стилю на решение @ Jack:
Ключевые отличия:
динамический SQL с
RETURN QUERY EXECUTE
.Поскольку мы переходим по шагам, другой план запроса может быть выгодным. План запроса для статического SQL генерируется один раз, а затем используется повторно, что может сэкономить некоторые накладные расходы. Но в этом случае запрос прост и значения очень разные. Динамический SQL будет большой победой.
Динамический
LIMIT
для каждого шага запроса.Это помогает несколькими способами: во-первых, строки выбираются только по мере необходимости. В сочетании с динамическим SQL это также может генерировать различные планы запросов для начала. Второе: нет необходимости в дополнительном
LIMIT
вызове функции для обрезки излишков.эталонный тест
Настроить
Я выбрал четыре примера и провел три разных теста с каждым. Я взял лучшее из пяти, чтобы сравнить с теплым кешем:
Необработанный SQL-запрос формы:
То же самое после создания этого индекса
Нужно примерно столько же места, сколько все мои частичные индексы вместе:
Функция
Полученные результаты
1: общее время выполнения: 315,458 мс
2: общее время выполнения: 36,458 мс
3: общее время выполнения: 0,330 мс
1: общее время выполнения: 294,819 мс
2: общее время выполнения: 18,915 мс
3: общее время выполнения: 1,414 мс
1: общее время выполнения: 426,831 мс
2: общее время выполнения: 217,874 мс
3: общее время выполнения: 1,611 мс
1: общее время выполнения: 2458,205 мс
2: общее время выполнения: 2458,205 мс - для больших диапазонов lset сканирование seq выполняется быстрее индекса.
3: общее время выполнения: 0,266 мс
Вывод
Как и ожидалось, выгода от функции возрастает с увеличением диапазона
lset
и уменьшениемLIMIT
.С очень маленькими диапазонами
lset
необработанный запрос в сочетании с индексом на самом деле быстрее . Вы захотите проверить и, возможно, выполнить ответвление: необработанный запрос для небольших диапазоновlset
, иначе вызов функции. Вы могли бы даже встроить это в функцию для «лучшего из двух миров» - вот что я бы сделал.В зависимости от вашего распределения данных и типичных запросов, дополнительные шаги
lex_freq
могут помочь производительности. Тест, чтобы найти сладкое место. С инструментами, представленными здесь, это должно быть легко проверить.источник
Я не вижу смысла включать слово столбец в указатель. Итак, этот индекс
сделает ваш запрос, чтобы выполнить быстро.
UPD
В настоящее время нет способов создать индекс покрытия в PostgreSQL. Об обсуждении этой функции в списке рассылки PostgreSQL было http://archives.postgresql.org/pgsql-performance/2012-06/msg00114.php.
источник
Использование индекса GIST
Это зависит от того, что вы имеете в виду, когда говорите быстро: вам, очевидно, нужно посетить каждую строку в диапазоне, потому что ваш запрос
ORDER freq DESC
. Застенчивый, что планировщик запросов уже покрывает это, если я понимаю вопрос,Здесь мы создаем таблицу с 10k строк
(5::int,random()::double precision)
Мы индексируем это,
Мы запрашиваем это,
Мы получаем
Seq Scan on t
. Это просто потому, что наши оценки селективности позволяют pg сделать вывод, что доступ к куче быстрее, чем сканирование индекса и перепроверка. Поэтому мы делаем его более сочным, добавляя более 1 000 000 строк,(42::int,random()::double precision)
которые не соответствуют нашему «диапазону».И тогда мы запрашиваем,
Вы можете увидеть здесь, мы завершили в 4.6 мс с индексом только сканирование ,
Расширение диапазона для включения всей таблицы приводит к следующему логическому сканированию, а увеличение его еще на миллиард строк приведет к еще одному сканированию индекса.
Итак, в итоге,
источник