62.1. Индексы B-дерева#

62.1. Индексы B-дерева

62.1. Индексы B-дерева #

62.1.1. Введение #

Tantor SE-1C включает реализацию стандартной структуры данных индекса btree (многопутевое сбалансированное дерево). Любой тип данных, который может быть отсортирован в хорошо определенном линейном порядке, может быть проиндексирован с помощью индекса btree. Единственное ограничение состоит в том, что запись индекса не может превышать примерно одну треть страницы (после сжатия TOAST, если применимо).

Поскольку каждый класс операторов btree накладывает порядок сортировки на свой тип данных, классы операторов btree (или, на самом деле, семейства операторов) стали использоваться как общее представление и понимание семантики сортировки в Tantor SE-1C. Поэтому они приобрели некоторые функции, которые выходят за рамки того, что было бы необходимо только для поддержки индексов btree, и части системы, которые находятся довольно далеко от AM btree, используют их.

62.1.2. Поведение классов операторов B-дерева #

Как показано в Таблица 34.2, класс операторов btree должен предоставлять пять операторов сравнения: <, <=, =, >= и >. Можно было бы ожидать, что оператор <> также будет частью класса операторов, но это не так, потому что в поиске по индексу использование оператора <> в предложении WHERE практически никогда не будет полезным. (В некоторых случаях планировщик рассматривает оператор <> как связанный с классом операторов btree, но он находит этот оператор через отрицательную связь оператора =, а не через pg_amop).

Когда несколько типов данных имеют почти идентичную семантику сортировки, их классы операторов могут быть сгруппированы в семейство операторов. Это выгодно, поскольку это позволяет планировщику делать выводы о сравнениях между различными типами данных. Каждый класс операторов внутри семейства должен содержать операторы одного типа (и связанные с ними вспомогательные функции) для своего типа входных данных, в то время как операторы и вспомогательные функции для сравнения разных типов данных в семействе могут быть "свободными". Рекомендуется включать полный набор операторов для сравнения разных типов данных в семействе, чтобы гарантировать, что планировщик может представить любые условия сравнения, которые он выводит из транзитивности.

Существуют некоторые основные предположения, которым должна удовлетворять семейство операторов btree:

  • Оператор = должен быть отношением эквивалентности; то есть, для всех ненулевых значений A, B, C типа данных:

    • A = A истинно (рефлексивное правило)

    • если A = B, то B = A (симметричный закон)

    • если A = B и B = C, то A = C (транзитивный закон)

  • Оператор < должен быть строгим отношением порядка; то есть, для всех ненулевых значений A, B, C:

    • A < A is false (закон о нерефлексивности)

    • если A < B и B < C, то A < C (транзитивный закон)

  • Кроме того, упорядочение является полным; то есть, для всех ненулевых значений A, B:

    • ровно одно из A < B, A = B и B < A истинно (закон трех частей)

    (Закон трихотомии оправдывает определение функции сравнения, конечно).

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

Для семейства операторов, поддерживающего несколько типов данных, вышеуказанные законы должны выполняться, когда A, B, C берутся из любых типов данных в семействе. Транзитивные законы являются самыми сложными для обеспечения, так как в случае перекрестных типов они представляют утверждения о согласованности поведения двух или трех разных операторов. Например, не будет работать, если поместить float8 и numeric в одно семейство операторов, по крайней мере, не с текущей семантикой, что значения numeric преобразуются в float8 для сравнения с float8. Из-за ограниченной точности float8 это означает, что существуют различные значения numeric, которые будут сравниваться равными с одним и тем же значением float8, и, следовательно, транзитивный закон не будет выполняться.

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

Должно быть ясно, почему индекс btree требует соблюдения этих правил в пределах одного типа данных: без них нет упорядочивания для упорядочивания ключей. Кроме того, поиск по индексу с использованием ключа сравнения другого типа данных требует, чтобы сравнения вели себя разумно для двух типов данных. Расширения до трех или более типов данных внутри семейства не являются строго обязательными для самого механизма индекса btree, но планировщик полагается на них в целях оптимизации.

62.1.3. Функции поддержки B-дерева #

Согласно Таблица 34.8, btree определяет одну обязательную и четыре дополнительные функции поддержки. Пять пользовательских методов включают:

order

Для каждой комбинации типов данных, для которых семейство операторов btree предоставляет операторы сравнения, оно должно предоставлять опорную функцию сравнения, зарегистрированную в pg_amproc с номером опорной функции 1 и amproclefttype/amprocrighttype, равными левому и правому типам данных для сравнения (т.е. тем же типам данных, с которыми зарегистрированы соответствующие операторы в pg_amop). Функция сравнения должна принимать два ненулевых значения A и B и возвращать значение int32, которое является < 0, 0 или > 0, когда A < B, A = B или A > B соответственно. Результат null запрещен: все значения типа данных должны быть сравнимыми. См. примеры в src/backend/access/nbtree/nbtcompare.c.

Если сравниваемые значения имеют тип данных, который поддерживает правило сортировки, соответствующий OID правила сортировки будет передан в функцию поддержки сравнения с использованием стандартного механизма PG_GET_COLLATION().

sortsupport

По желанию, семейство операторов btree может предоставлять функции поддержки сортировки, зарегистрированные под номером 2. Эти функции позволяют реализовать сравнения для целей сортировки более эффективным способом, чем просто вызов опорные функции сравнения. Соответствующие API определены в файле src/include/utils/sortsupport.h.

in_range

По желанию, семейство операторов btree может предоставлять опорные функции in_range, зарегистрированные под номером 3. Они не используются во время операций индексирования btree; вместо этого они расширяют семантику семейства операторов таким образом, чтобы оно могло поддерживать оконные предложения, содержащие типы границ рамки RANGE offset PRECEDING и RANGE offset FOLLOWING (см. Раздел 4.2.8). В основном, предоставляемая дополнительная информация определяет, как добавить или вычесть значение offset таким образом, чтобы оно было совместимо с упорядочиванием данных семейства.

Функция in_range должна иметь сигнатуру

in_range(val type1, base type1, offset type2, sub bool, less bool)
returns bool

val и base должны быть одного типа, который является одним из типов, поддерживаемых семейством операторов (т.е. типом, для которого он предоставляет упорядочение). Однако, offset может быть другого типа, который может быть неподдерживаемым семейством. Примером является встроенное семейство time_ops, которое предоставляет функцию in_range, у которой offset имеет тип interval. Семейство может предоставлять функции in_range для любого из поддерживаемых типов и одного или нескольких типов offset. Каждая функция in_range должна быть введена в pg_amproc с amproclefttype, равным type1, и amprocrighttype, равным type2.

Существенная семантика функции in_range зависит от двух логических флаговых параметров. Она должна добавить или вычесть base и offset, затем сравнить val с результатом, следующим образом:

  • если !sub и !less, вернуть val >= (base + offset)

  • если !sub и less, вернуть val <= (base + offset)

  • если sub и !less, вернуть val >= (base - offset)

  • если sub и less, вернуть val <= (base - offset)

Перед этим функция должна проверить знак переменной offset: если он меньше нуля, вызвать ошибку ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE (22013) с текстом ошибки вида недопустимый размер предшествующего или следующего значения в оконной функции. (Это требуется стандартом SQL, хотя нестандартные семейства операторов, возможно, могут игнорировать это ограничение, так как, кажется, нет особой семантической необходимости в нем). Это требование делегируется функции in_range, чтобы основной код не должен был понимать, что означает меньше нуля для конкретного типа данных.

Дополнительное ожидание состоит в том, что функции in_range должны, если это возможно, избегать генерации ошибки, если base + offset или base - offset приведут к переполнению. Правильный результат сравнения может быть определен даже если это значение будет выходить за диапазон типа данных. Обратите внимание, что если тип данных включает понятия, такие как бесконечность или NaN, необходимо удостовериться, что результаты функции in_range согласуются с нормальным порядком сортировки семейства операторов.

Результаты функции in_range должны быть согласованы с порядком сортировки, установленным операторной семьей. А именно, при заданных значениях offset и sub, должно выполняться следующее:

  • Если in_range с less = true истинно для некоторых val1 и base, то оно должно быть истинно для каждого val2 <= val1 с тем же base.

  • Если in_range с less = true равно false для некоторых val1 и base, то оно должно быть false для каждого val2 >= val1 с тем же base.

  • Если in_range с less = true истинно для некоторого val и base1, то оно должно быть истинно для каждого base2 >= base1 с тем же val.

  • Если in_range с less = true равно false для некоторых val и base1, то оно должно быть false для каждого base2 <= base1 с тем же val.

Аналогичные операторы с инвертированными условиями выполняются, когда less = false.

Если заказываемый тип (type1) является сортируемым, соответствующий OID правила сортировки будет передан в функцию in_range с использованием стандартного механизма PG_GET_COLLATION().

in_range функции не обязаны обрабатывать NULL входные данные и обычно будут помечены как строгие.

equalimage

По желанию, семейство операторов btree может предоставлять опорные функции equalimage (равенство подразумевает равенство изображения), зарегистрированные под номером 4. Эти функции позволяют основному коду определить, когда безопасно применять оптимизацию дедупликации btree. В настоящее время функции equalimage вызываются только при создании или перестроении индекса.

Функция equalimage должна иметь сигнатуру

equalimage(opcintype oid) returns bool

Возвращаемое значение представляет собой статическую информацию о классе операторов и сортировке. Возвращение true указывает, что функция order для класса операторов гарантированно возвращает только 0 (аргументы равны), когда его аргументы A и B также взаимозаменяемы без потери семантической информации. Не регистрирование функции equalimage или возвращение false указывает на то, что это условие не может быть считано верным.

Аргумент opcintype является pg_type.oid типа данных, индексируемого классом операторов. Это удобство, позволяющее повторно использовать одну и ту же базовую функцию equalimage в разных классах операторов. Если opcintype является сортируемым типом данных, соответствующий OID правила сортировки будет передан функции equalimage с использованием стандартного механизма PG_GET_COLLATION().

С точки зрения класса операторов, возвращение true указывает на то, что дедупликация безопасна (или безопасна для правила сортировки, идентификатор которого был передан в функцию equalimage). Однако, основной код будет считать дедупликацию безопасной только для индекса, когда каждый индексируемый столбец использует класс операторов, который регистрирует функцию equalimage, и каждая функция действительно возвращает true при вызове.

Равенство изображений почти то же самое условие, что и простое побитовое равенство. Есть одно тонкое отличие: при индексировании типа данных varlena, представление на диске двух равных изображений может не быть побитово равным из-за несогласованного применения сжатия TOAST на входе. Формально, когда функция equalimage класса операторов возвращает true, можно считать, что функция datum_image_eq() на языке C всегда согласуется с функцией order класса операторов (при условии, что один и тот же OID правила сортировки передается и в функцию equalimage, и в функцию order).

Основной код фундаментально неспособен вывести какую-либо информацию о статусе равенство подразумевает равенство изображения класса операторов внутри семейства множественных типов данных, основываясь на деталях других классов операторов в том же семействе. Кроме того, не имеет смысла для семейства операторов зарегистрировать функцию equalimage для перекрестного типа, и попытка сделать это приведет к ошибке. Это связано с тем, что статус равенство подразумевает равенство изображения не зависит только от семантики сортировки/равенства, которая более или менее определена на уровне семейства операторов. Обычно, семантику, которую реализует конкретный тип данных, необходимо рассматривать отдельно.

Конвенция, которую следуют классы операторов, включенные в базовую дистрибуцию Tantor SE-1C, заключается в регистрации стандартной обобщенной функции equalimage. Большинство классов операторов регистрируют btequalimage(), что указывает на безопасность дедупликации безусловно. Классы операторов для сравнимых типов данных, таких как text, регистрируют btvarstrequalimage(), что указывает на безопасность дедупликации с детерминированными правилами сортировки. Лучшей практикой для сторонних расширений является регистрация собственной пользовательской функции для сохранения контроля.

options

По желанию, семейство операторов B-дерева может предоставлять опорные функции options (опции, специфичные для класса операторов), зарегистрированные под номером 5. Эти функции определяют набор пользовательских параметров, которые управляют поведением класса операторов.

Опорная функция options должна иметь сигнатуру

options(relopts local_relopts *) returns void

Функции передается указатель на структуру local_relopts, которую необходимо заполнить набором специфических для класса операторов опций. Опции могут быть доступны из других вспомогательных функций с использованием макросов PG_HAS_OPCLASS_OPTIONS() и PG_GET_OPCLASS_OPTIONS().

В настоящее время ни один класс операторов B-Tree не имеет функции поддержки options. B-дерево не позволяет гибкого представления ключей, как это делают GiST, SP-GiST, GIN и BRIN. Поэтому, вероятно, options не имеет большого применения в текущем методе доступа к индексу B-дерева. Тем не менее, эта Опорная функция была добавлена в B-дерево для обеспечения единообразия и, вероятно, найдет применение во время дальнейшего развития B-дерева в Tantor SE-1C.

62.1.4. Реализация #

Этот раздел описывает детали реализации индекса B-Tree, которые могут быть полезны опытным пользователям. См. файл src/backend/access/nbtree/README в исходном дистрибутиве для более подробного описания реализации B-Tree с фокусом на внутренних механизмах.

62.1.4.1. Структура B-дерева #

Tantor SE-1C B-Tree индексы являются многоуровневыми структурами дерева, где каждый уровень дерева может быть использован в качестве двусвязного списка страниц. Одна метастраница хранится в фиксированной позиции в начале первого сегментного файла индекса. Все остальные страницы являются либо листовыми страницами, либо внутренними страницами. Листовые страницы - это страницы на самом нижнем уровне дерева. Все остальные уровни состоят из внутренних страниц. Каждая листовая страница содержит кортежи, указывающие на строки таблицы. Каждая внутренняя страница содержит кортежи, указывающие на следующий уровень вниз по дереву. Обычно более 99% всех страниц являются листовыми страницами. Как внутренние, так и листовые страницы используют стандартный формат страницы, описанный в Раздел 63.6.

Новые листовые страницы добавляются в индекс B-дерева, когда существующая листовая страница не может вместить входящий кортеж. Операция разделения страницы освобождает место для элементов, которые изначально принадлежали переполненной странице, перемещая часть элементов на новую страницу. При разделении страницы также вставляется новая ссылка на нижний уровень на новую страницу в родительскую страницу, что может привести к разделению родительской страницы в свою очередь. Разделение страниц распространяется вверх рекурсивным образом. Когда корневая страница наконец не может вместить новую ссылку на нижний уровень, выполняется операция разделения корневой страницы. Это добавляет новый уровень в структуру дерева, создавая новую корневую страницу, которая находится на один уровень выше исходной корневой страницы.

62.1.4.2. Удаление индекса снизу вверх #

B-Tree индексы не знают о том, что в рамках MVCC может существовать несколько версий одной и той же логической строки таблицы; для индекса каждый кортеж является независимым объектом, который требует своей собственной записи в индексе. Кортежи Version churn иногда могут накапливаться и негативно влиять на задержку и пропускную способность запросов. Это обычно происходит при работе с нагрузкой, где преобладают операции UPDATE, и большинство отдельных обновлений не могут использовать оптимизацию HOT. Изменение значения только одного столбца, покрытого одним индексом, во время UPDATE всегда требует нового набора индексных кортежей — по одному для каждого индекса на таблице. Обратите внимание, что это включает индексы, которые не были логически изменены операцией UPDATE. Все индексы будут нуждаться в последующем физическом кортеже индекса, который указывает на последнюю версию в таблице. Каждый новый кортеж в каждом индексе, как правило, должен сосуществовать с исходным обновленным кортежем в течение короткого периода времени (обычно до непосредственно после метки транзакции UPDATE).

Индексы B-Tree пошагово удаляют версии устаревших кортежей индекса, выполняя проходы удаления индекса снизу вверх. Каждый проход удаления запускается в ответ на ожидаемое разделение страницы из-за изменения версии. Это происходит только с индексами, которые не изменяются логически с помощью операторов UPDATE, иначе на определенных страницах могло бы накапливаться большое количество устаревших версий. Обычно разделение страницы избегается, хотя возможно, что некоторые эвристические алгоритмы на уровне реализации не смогут идентифицировать и удалить ни одного ненужного кортежа индекса (в этом случае разделение страницы или проход дедупликации разрешает проблему входящего нового кортежа, который не помещается на листовую страницу). Максимальное количество версий, которые должен просмотреть любой сканирующий индекс (для любой отдельной логической строки), является важным фактором общей отзывчивости и пропускной способности системы. Проход удаления индекса снизу вверх нацелен на предполагаемые ненужные кортежи на одной листовой странице на основе качественных различий, касающихся логических строк и версий. Это отличается от сверху вниз очистки индекса, выполняемой рабочими процессами автоочистки, которая запускается, когда превышаются определенные количественные пороги на уровне таблицы (см. Раздел 23.1.6).

Примечание

Не все операции удаления, выполняемые внутри индексов B-дерева, являются операциями удаления снизу вверх. Существует отдельная категория удаления кортежей индекса: удаление простого кортежа индекса. Это операция отложенного обслуживания, которая удаляет кортежи индекса, которые известно, что можно безопасно удалить (те, у которых бит LP_DEAD в идентификаторе элемента уже установлен). Как и удаление индекса снизу вверх, удаление простого кортежа индекса происходит в тот момент, когда ожидается разделение страницы, чтобы избежать разделения.

Простое удаление является оптимистичным в том смысле, что оно может произойти только тогда, когда недавние сканирования индекса устанавливают биты LP_DEAD для затронутых элементов в процессе прохождения. До версии Tantor SE-1C 14 единственная категория удаления в B-дереве было простое удаление. Основные различия между ним и удалением снизу вверх заключаются в том, что только первое оптимистически управляется активностью проходящих сканирований индекса, в то время как только последнее специально нацелено на версионные изменения от UPDATE, которые логически не изменяют индексированные столбцы.

Удаление индексов снизу вверх выполняет подавляющее большинство всех операций очистки мусорных кортежей индекса для определенных индексов с определенной нагрузкой. Это ожидаемо для любого индекса B-Tree, который подвержен значительным изменениям версий от команд UPDATE, которые редко или никогда логически не изменяют столбцы, которые покрывает индекс. Среднее и наихудшее количество версий на логическую строку можно поддерживать на низком уровне только через целенаправленные инкрементальные проходы удаления. Вполне возможно, что размер индексов на диске никогда не увеличится даже на одну страницу/блок, несмотря на постоянные изменения версий от команд UPDATE. Даже в этом случае, исчерпывающая "чистка" операцией VACUUM (обычно выполняемая в рабочем процессе автоматической очистки) в конечном итоге потребуется в качестве части коллективной очистки таблицы и каждого из ее индексов.

В отличие от VACUUM, удаление индекса снизу вверх не обеспечивает никаких надежных гарантий относительно того, насколько старыми могут быть самые старые мусорные кортежи индекса. Ни одному индексу не разрешается сохранять мусорные кортежи индекса, которые стали мертвыми до консервативной точки отсечения, общей для таблицы и всех ее индексов в совокупности. Этот фундаментальный инвариант на уровне таблицы обеспечивает безопасность повторного использования идентификаторов строк таблицы TID. Именно таким образом возможно, чтобы различные логические строки повторно использовали один и тот же идентификатор строки таблицы TID со временем (хотя это никогда не может произойти с двумя логическими строками, чьи сроки службы охватывают один и тот же цикл VACUUM).

62.1.4.3. Дедупликация #

Дубликатом является кортеж листовой страницы (кортеж, указывающий на строку таблицы), в котором все индексные ключевые столбцы имеют значения, совпадающие с соответствующими значениями столбцов из по крайней мере одного другого кортежа листовой страницы в том же индексе. Дублирующиеся кортежи довольно распространены на практике. Индексы В-дерева могут использовать специальное, экономичное по месту представление для дубликатов, когда используется необязательная техника: дедупликация.

Дедупликация работает путем периодического объединения групп дублирующихся кортежей, формируя для каждой группы единственный кортеж списка публикаций. Значение(я) ключа столбца появляется только один раз в этом представлении. Затем следует отсортированный массив TID, указывающий на строки в таблице. Это значительно уменьшает размер хранения индексов, где каждое значение (или каждая отдельная комбинация значений столбцов) в среднем появляется несколько раз. Задержка запросов может быть значительно снижена. Общая пропускная способность запросов может значительно увеличиться. Затраты на регулярную очистку индексов также могут быть значительно снижены.

Примечание

B-Tree дедупликация так же эффективна с дубликатами, содержащими значение NULL, даже если значения NULL никогда не равны друг другу согласно оператору = любого класса операторов B-Tree. Для любой части реализации, которая понимает структуру B-Tree на диске, NULL является просто еще одним значением из области индексированных значений.

Процесс дедупликации происходит лениво, когда вставляется новый элемент, который не может поместиться на существующей листовой странице, хотя только в том случае, если удаление индексного кортежа не освободило достаточно места для нового элемента (обычно удаление кратковременно рассматривается, а затем пропускается). В отличие от кортежей списка публикаций GIN, кортежи списка публикаций B-дерева не нуждаются в расширении каждый раз при вставке нового дубликата; они являются всего лишь альтернативным физическим представлением исходного логического содержимого листовой страницы. Этот дизайн приоритезирует последовательную производительность при смешанных рабочих нагрузках чтения и записи. Большинство клиентских приложений, по крайней мере, получат умеренную выгоду от использования дедупликации. Дедупликация включена по умолчанию.

CREATE INDEX и REINDEX применяют дедупликацию для создания кортежей списка публикаций, хотя стратегия, которую они используют, немного отличается. Каждая группа дублирующихся обычных кортежей, встреченных в отсортированном вводе из таблицы, объединяется в кортеж списка публикаций перед добавлением на текущую ожидающую листовую страницу. Отдельные кортежи списка публикаций упаковываются с максимальным количеством TID. Листовые страницы записываются обычным образом, без отдельного прохода по дедупликации. Эта стратегия хорошо подходит для CREATE INDEX и REINDEX, потому что они выполняются однократно пакетными операциями.

Рабочие нагрузки, при которых выгода от удаления дубликатов отсутствует из-за наличия небольшого количества или отсутствия дублирующихся значений в индексах, будут иметь небольшое, фиксированное снижение производительности (если удаление дубликатов явно не отключено). Параметр хранения deduplicate_items может быть использован для отключения удаления дубликатов в отдельных индексах. В случае только чтения нагрузки никогда не возникает снижение производительности, так как чтение кортежей списка публикаций по крайней мере так же эффективно, как чтение стандартного представления кортежа. Отключение удаления дубликатов обычно не является полезным.

Иногда уникальные индексы (а также уникальные ограничения) могут использовать дедупликацию. Это позволяет временно "поглотить" дубликаты версий на листовых страницах. Дедупликация в уникальных индексах дополняет стратегию удаления индекса снизу вверх, особенно в случаях, когда долговременная транзакция удерживает снимок, который блокирует сборку мусора. Цель состоит в том, чтобы купить время для повторного вступления в силу стратегии удаления индекса снизу вверх. Отсрочка разделения страниц до тех пор, пока не исчезнет единственная долговременная транзакция, может позволить успешно выполнить проход удаления снизу вверх, где ранее проход удаления не удался.

Подсказка

Применяется специальный эвристический алгоритм для определения, должен ли выполняться проход по дедупликации в уникальном индексе. Часто можно сразу перейти к разделению листовой страницы, избегая штрафа в производительности от потери циклов на бесполезные проходы по дедупликации. Если вам волнует издержки на дедупликацию, рассмотрите возможность выборочного отключения параметра deduplicate_items = off. Оставление дедупликации включенной в уникальных индексах имеет небольшой недостаток.

Дедупликация не может быть использована во всех случаях из-за ограничений на уровне реализации. Безопасность дедупликации определяется при выполнении команды CREATE INDEX или REINDEX.

Обратите внимание, что дедупликация считается небезопасной и не может использоваться в следующих случаях, связанных с семантически значимыми различиями между равными данными:

  • text, varchar и char не могут использовать дедупликацию, когда используется недетерминированное правило сортировки. Различия в регистре и акцентах должны быть сохранены между равными данными.

  • numeric не может использовать дедупликацию. Числовая шкала отображения должна быть сохранена для одинаковых данных.

  • jsonb не может использовать дедупликацию, так как класс операторов B-Tree для jsonb использует внутренне тип numeric.

  • float4 и float8 не могут использовать дедупликацию. У этих типов есть отдельные представления для -0 и 0, которые, тем не менее, считаются равными. Это различие должно быть сохранено.

Есть еще одно ограничение на уровне реализации, которое может быть снято в будущих версиях Tantor SE-1C:

  • Типы контейнеров (такие как составные типы, массивы или типы диапазонов) не могут использовать дедупликацию.

Существует еще одно ограничение на уровне реализации, которое применяется независимо от класса оператора или правила сортировки:

  • INCLUDE индексы никогда не могут использовать дедупликацию.