11.9. Сканирование только индексов и покрывающие индексы#
11.9. Сканирование только индексов и покрывающие индексы #
Все индексы в Tantor SE являются вторичными индексами, что означает, что каждый индекс хранится отдельно от основной области данных таблицы (которая называется кучей в терминологии Tantor SE). Это означает, что при обычном сканировании индекса каждое извлечение строки требует получения данных как из индекса, так и из кучи. Кроме того, хотя записи индекса, которые соответствуют заданному условию WHERE
, обычно находятся близко друг к другу в индексе, строки таблицы, на которые они ссылаются, могут находиться где угодно в куче. Часть сканирования индекса, связанная с доступом к куче, включает множество случайных доступов к куче, что может быть медленным, особенно на традиционных вращающихся носителях информации. (Как описано в разделе Раздел 11.5, сканирование по битовым картам пытается снизить эту стоимость, выполняя доступы к куче в отсортированном порядке, но это имеет свои ограничения).
Для решения этой проблемы производительности Tantor SE поддерживает индексные сканирования только по индексу, которые могут отвечать на запросы только с использованием индекса, без доступа к куче. Основная идея заключается в возвращении значений непосредственно из каждой записи индекса, а не в обращении к связанной записи в куче. Есть два основных ограничения на использование этого метода:
Тип индекса должен поддерживать индексные сканирования. B-деревья индексов всегда это делают. Индексы GiST и SP-GiST поддерживают индексные сканирования для некоторых классов операторов, но не для других. Другие типы индексов не имеют поддержки. Основное требование состоит в том, чтобы индекс физически хранил или мог восстановить исходное значение данных для каждой записи индекса. В качестве контрпримера, индексы GIN не могут поддерживать индексные сканирования, потому что каждая запись индекса обычно содержит только часть исходного значения данных.
Запрос должен ссылаться только на столбцы, хранящиеся в индексе. Например, при наличии индекса на столбцах
x
иy
таблицы, которая также имеет столбецz
, эти запросы могут использовать индексные сканирования только:SELECT x, y FROM tab WHERE x = 'key'; SELECT x FROM tab WHERE x = 'key' AND y < 42;
но эти запросы не могут:
SELECT x, z FROM tab WHERE x = 'key'; SELECT x FROM tab WHERE x = 'key' AND z < 42;
(Индексы выражений и частичные индексы усложняют это правило, как обсуждается ниже).
Если эти два основных требования выполняются, то все значения данных, необходимые для запроса, доступны из индекса, поэтому индексированный только скан физически возможен. Но для любого сканирования таблицы в Tantor SEтребуется дополнительное условие: необходимо проверить, что каждая извлеченная строка видима для моментального снимка MVCC запроса, как описано в разделе Глава 13. Информация о видимости не хранится в записях индекса, только в записях кучи; поэтому на первый взгляд кажется, что каждое извлечение строки требует доступа к куче. И это действительно так, если строка таблицы была недавно изменена. Однако для редко изменяющихся данных есть способ обойти эту проблему. Tantor SEотслеживает, для каждой страницы в куче таблицы, все ли строки, хранящиеся на этой странице, достаточно старые, чтобы быть видимыми для всех текущих и будущих транзакций. Эта информация хранится в бите в карте видимости таблицы. При индексированном только сканировании, после нахождения подходящей записи индекса, проверяется бит карты видимости для соответствующей страницы кучи. Если он установлен, строка считается видимой, и данные могут быть возвращены без дополнительных действий. Если он не установлен, запись кучи должна быть посещена, чтобы узнать, видима ли она, поэтому преимущество в производительности по сравнению со стандартным сканированием индекса не получается. Даже в случае успешного выполнения, этот подход заменяет доступы к карте видимости доступами к куче; но поскольку карта видимости на четыре порядка меньше, чем описываемая ею куча, для доступа к ней требуется гораздо меньше физического ввода-вывода. В большинстве ситуаций карта видимости остается кешированной в памяти все время.
Вкратце, хотя индексированный только-скан возможен при соблюдении двух основных требований, он будет выигрышным только в том случае, если значительная часть страниц кучи таблицы имеет установленные биты карты полной видимости. Однако таблицы, в которых большая часть строк неизменна, достаточно распространены, чтобы этот тип сканирования был очень полезен на практике.
Для эффективного использования функции индексированного сканирования, вы можете выбрать создание покрывающего индекса, который представляет собой индекс, специально разработанный для включения столбцов, необходимых для определенного типа запроса, который вы часто выполняете. Поскольку запросы обычно требуют извлечения большего количества столбцов, чем только те, по которым они выполняют поиск, Tantor SEпозволяет создавать индекс, в котором некоторые столбцы являются просто полезной нагрузкой и не являются частью ключа поиска. Это делается путем добавления предложения INCLUDE
, в которой перечисляются дополнительные столбцы. Например, если вы часто выполняете запросы вроде
SELECT y FROM tab WHERE x = 'key';
традиционным подходом к ускорению таких запросов было бы создание индекса только на x
. Однако, индекс, определенный как
CREATE INDEX tab_x_y ON tab(x) INCLUDE (y);
может обрабатывать эти запросы как индексные сканирования без обращения к куче, потому что y
может быть получено из индекса без посещения кучи.
Поскольку столбец y
не является частью ключа поиска индекса, он не обязан иметь тип данных, который может обрабатывать индекс; он просто хранится в индексе и не интерпретируется механизмом индекса. Кроме того, если индекс является уникальным индексом, то
CREATE UNIQUE INDEX tab_x_y ON tab(x) INCLUDE (y);
условие уникальности применяется только к столбцу x
,
а не к комбинации x
и y
.
(Предложение INCLUDE
также можно записать
в ограничения UNIQUE
и PRIMARY KEY
,
предоставляя альтернативный синтаксис для настройки индекса, подобного
этому).
It's wise to be conservative about adding non-key payload columns to an index, especially wide columns. If an index tuple exceeds the maximum size allowed for the index type, data insertion will fail. In any case, non-key columns duplicate data from the index's table and bloat the size of the index, thus potentially slowing searches. And remember that there is little point in including payload columns in an index unless the table changes slowly enough that an index-only scan is likely to not need to access the heap. If the heap tuple must be visited anyway, it costs nothing more to get the column's value from there. Other restrictions are that expressions are not currently supported as included columns, and that only B-дерево, GiST and SP-GiST indexes currently support included columns.
До того, как Tantor SE получил
функцию INCLUDE
, люди иногда создавали покрывающие
индексы, записывая столбцы данных как обычные индексные столбцы,
то есть записывая
CREATE INDEX tab_x_y ON tab(x, y);
даже если они никогда не собирались использовать y
в
составе предложения WHERE
. Это работает нормально, пока
дополнительные столбцы являются завершающими столбцами; делать их ведущими столбцами
неразумно по причинам, объясненным в Раздел 11.3.
Однако, этот метод не поддерживает случай, когда нужно, чтобы индекс
обеспечивал уникальность на ключевом столбце(ах).
Обрезка суффикса всегда удаляет не ключевые столбцы с верхних уровней B-дерева. В качестве полезных столбцов они никогда не используются для направления сканирования индекса. В процессе обрезки также удаляется один или несколько последних ключевых столбцов, когда оставшийся префикс ключевых столбцов достаточен для описания кортежей на самом нижнем уровне B-дерева. На практике, покрывающие индексы без предложения INCLUDE
часто избегают хранения столбцов, которые фактически являются полезной нагрузкой на верхних уровнях. Однако, явное определение полезных столбцов как не ключевых столбцов надежно делает кортежи на верхних уровнях маленькими.
В принципе, индексные сканирования могут использоваться с выражениями индексов.
Например, при наличии индекса на f(x)
,
где x
- столбец таблицы, должна быть возможность выполнить
SELECT f(x) FROM tab WHERE f(x) < 1;
как индексно-только сканирование; и это очень привлекательно, если f()
- это функция, требующая значительных вычислительных затрат. Однако планировщик Tantor SE в настоящее время не очень умный в таких случаях. Он считает, что запрос может быть выполнен только с помощью индексно-только сканирования, когда все столбцы, необходимые для запроса, доступны из индекса. В этом примере x
не нужен, кроме контекста f(x)
, но планировщик не замечает этого и приходит к выводу, что индексно-только сканирование невозможно. Если индексно-только сканирование кажется достаточно ценным, это можно обойти, добавив x
в качестве включенного столбца, например
CREATE INDEX tab_f_x ON tab (f(x)) INCLUDE (x);
Дополнительное предостережение, если целью является избежание повторного вычисления f(x)
, то планировщик не обязательно будет соотносить использование f(x)
, которое не находится в индексируемых предложениях WHERE
, с индексным столбцом. Обычно он правильно справляется с этим в простых запросах, как показано выше, но не в запросах, которые включают соединения. Эти недостатки могут быть устранены в будущих версиях Tantor SE.
Частичные индексы также имеют интересные взаимодействия с индексированным только сканированием. Рассмотрим частичный индекс, показанный в Пример 11.3:
CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target) WHERE success;
В принципе, можно выполнить индексированный только сканирование этого индекса для удовлетворения запроса, например
SELECT target FROM tests WHERE subject = 'some-subject' AND success;
Есть одна проблема: предложение WHERE
ссылается на success
, которое не доступно в качестве столбца результата индекса. Тем не менее, возможно выполнение сканирования только индексов, поскольку план не требует повторной проверки этой части предложения WHERE
во время выполнения: все записи, найденные в индексе, обязательно имеют success = true
, поэтому это не требует явной проверки в плане. Версии PostgreSQL 9.6 и более поздние распознают такие случаи и позволяют генерировать индексные сканирования только по индексу, но более старые версии этого не делают.