Все индексы в Digital Q.DataBase
являются вторичными индексами; это означает, что каждый индекс хранится отдельно от основной области данных таблицы (которая в терминологии куча
называется Digital Q.DataBase ). Это означает, что при обычном сканировании индекса для извлечения каждой строки требуется получение данных как из индекса, так и из кучи. Кроме того, хотя записи индекса, соответствующие заданному индексируемому WHERE условию обычно расположены в индексе близко друг к другу, строки таблицы, на которые они ссылаются, могут находиться в произвольном месте кучи. Таким образом, этап доступа к куче при сканировании индекса сопряжен с большим количеством операций произвольного доступа, что может замедлять работу, особенно на традиционных магнитных дисках. (Как описано в
Раздел 2.8.5, сканирование по битовой карте позволяет снизить эти издержки за счет выполнения обращений к куче в отсортированном порядке, но это эффективно лишь до определенной степени.)
Для решения этой проблемы производительности Digital Q.DataBase поддерживает сканирование только по индексу, который позволяет обрабатывать запросы на основе одного лишь индекса без обращения к куче. Основная идея заключается в том, чтобы возвращать значения непосредственно из каждой записи индекса вместо обращения к связанной записи в куче. Существуют два основных ограничения на использование данного метода:
Тип индекса должен поддерживать сканирование только по индексу. Индексы 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;
(Индексы по выражениям и частичные индексы усложняют это правило, как будет показано ниже.)
При соблюдении этих двух фундаментальных требований все данные, необходимые для выполнения запроса, могут быть получены из индекса, что делает физически возможным применение сканирования только по индексу. Однако существует дополнительное требование для любого сканирования таблицы в Digital Q.DataBase: необходимо подтвердить, что каждая извлеченная строка является «видимой» для снимка MVCC данного запроса, как описано в Глава 2.10. Информация о видимости не сохраняется в записях индекса, она содержится только в записях кучи; На первый взгляд может показаться, что каждое извлечение строки в любом случае потребует обращения к куче. И это действительно так, если строка таблицы была недавно изменена. Однако для редко изменяемых данных существует способ обойти эту проблему. Digital Q.DataBase отслеживает для каждой страницы в куче таблицы, являются ли все хранящиеся на ней строки достаточно старыми, чтобы быть видимыми для всех текущих и будущих транзакций. Эта информация хранится в виде бита в табличной карте видимости. При сканировании только по индексу после нахождения потенциальной записи индекса проверяется бит в карте видимости для соответствующей страницы кучи. Если он установлен, строка считается видимой, и данные могут быть возвращены без дополнительных действий. Если он не установлен, необходимо обратиться к записи в куче, чтобы проверить её видимость, поэтому преимущество в производительности по сравнению со стандартным сканированием индекса не достигается. Даже в случае успеха данный подход заменяет обращения к куче обращениями к карте видимости; но поскольку карта видимости на четыре порядка меньше кучи, которую она описывает, для доступа к ней требуется гораздо меньше физических операций ввода-вывода. В большинстве случаев карта видимости постоянно находится в кэше памяти.
Таким образом, хотя сканирование только по индексу возможно при выполнении двух основных условий, оно обеспечит преимущество в производительности только в том случае, если у значительной части страниц кучи в таблице установлены соответствующие биты в карте видимости. Тем не менее таблицы, в которых большая часть строк не изменяется, встречаются достаточно часто, что делает данный тип сканирования крайне полезным на практике.
Чтобы эффективно использовать функциональность сканирования только по индексу, вы можете создать покрывающий индекс, который представляет собой индекс, специально разработанный для включения столбцов, необходимых для определенного типа часто выполняемых запросов. Поскольку в запросах обычно требуется извлекать больше столбцов, чем задействовано в условиях поиска, Digital Q.DataBase позволяет создать индекс,
в котором некоторые столбцы являются лишь «полезной нагрузкой» и не входят в состав
ключа поиска. Это реализуется путем добавления INCLUDE
предложения со списком дополнительных столбцов. Например, если вы часто выполняете
запросы вида
SELECT y FROM tab WHERE x = 'key';
традиционный подход к ускорению таких запросов заключается в создании индекса только по x столбцу 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
ограничений, предоставляя альтернативный синтаксис для создания подобного индекса.)
Рекомендуется осмотрительно подходить к добавлению в индекс неключевых столбцов полезной нагрузки, особенно широких столбцов. Если индексный кортеж превысит максимальный размер, допустимый для данного типа индекса, вставка данных завершится ошибкой. В любом случае неключевые столбцы дублируют данные из таблицы и увеличивают размер индекса, что потенциально замедляет поиск. Также следует помнить, что включение столбцов полезной нагрузки в индекс имеет смысл только в том случае, если таблица изменяется достаточно редко, чтобы при сканировании только по индексу не требовалось обращение к куче. Если кортеж в куче все равно необходимо прочитать, извлечение значения столбца оттуда не влечет дополнительных затрат. К прочим ограничениям относится то, что в настоящее время выражения не поддерживаются в качестве включаемых столбцов, а поддержку включаемых столбцов имеют только B-дерево, индексы GiST и индексы SP-GiST.
До того как Digital Q.DataBase появился
функционал INCLUDE появилась эта функциональность, пользователи иногда создавали покрывающие индексы, записывая столбцы полезной нагрузки как обычные столбцы индекса, то есть указывая
CREATE INDEX tab_x_y ON tab(x, y);
даже если они не собирались когда-либо использовать y как
часть WHERE потребуется отдельный этап сортировки. Этот подход работает корректно, пока дополнительные столбцы являются замыкающими; делать их ведущими столбцами нецелесообразно по причинам, изложенным в Раздел 2.8.3. Однако данный метод не поддерживает случаи, когда требуется, чтобы индекс обеспечивал уникальность ключевого столбца (или столбцов).
Усечение суффиксов всегда удаляет неключевые
столбцы с верхних уровней B-дерева. Являясь столбцами полезной нагрузки, они никогда не используются для управления сканированием индекса. Процесс усечения также удаляет один или несколько замыкающих ключевых столбцов, когда оставшегося префикса ключевых столбцов оказывается достаточно для описания кортежей на самом низком уровне B-дерева. На практике покрывающие индексы без
предложения INCLUDE часто позволяют избежать хранения на верхних уровнях столбцов, которые фактически являются полезной нагрузкой. Однако
явное определение столбцов полезной нагрузки как неключевых столбцов
гарантированно позволяет сохранять кортежи на верхних уровнях компактными.
В принципе, сканирование только по индексу может применяться для индексов по выражениям.
Например, если создан индекс по f(x)
где x является столбцом таблицы, должна быть возможность
выполнить
SELECT f(x) FROM tab WHERE f(x) < 1;
как сканирование только по индексу; и это дает большое преимущество,
если f() является вычислительно затратной функцией.
Однако, Digital Q.DataBaseпланировщик в настоящее время недостаточно эффективно обрабатывает такие случаи. Он рассматривает запрос как потенциально пригодный для сканирования только по индексу лишь тогда, когда все столбцы,
необходимые для выполнения запроса, содержатся в индексе. В данном
примере, x не требуется, за исключением контекста f(x), однако планировщик запросов не учитывает этот факт и делает вывод о невозможности применения сканирования только по индексу. Если сканирование только по индексу представляется целесообразным, данную проблему можно решить путем добавления x в качестве включенного столбца, например:
CREATE INDEX tab_f_x ON tab (f(x)) INCLUDE (x);
Дополнительное предостережение: если целью является исключение повторных вычислений f(x), то планировщик запросов не всегда может сопоставить случаи использования f(x) которые не являются индексируемыми WHERE предложениях, со столбцом индекса. Как правило, это корректно работает в простых запросах, подобных приведенному выше, но не в запросах, использующих соединения. Эти недостатки могут быть устранены в будущих версиях Digital Q.DataBase.
Частичные индексы также имеют интересные особенности взаимодействия со сканированием только по индексу. Рассмотрим частичный индекс, приведенный в Пример 2.8.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 и, следовательно, явная проверка в плане не требуется. Digital Q.DataBase версии 9.6 и выше будут распознавать такие случаи и позволять использовать сканирование только по индексу, тогда как более старые версии этого не поддерживают.