64.1. Индексы B-деревья #
64.1.1. Введение #
PostgreSQL включает реализацию стандартной индексной структуры данных — B-дерева (btree, многонаправленного сбалансированного дерева). В индекс-B-дерево могут быть загружены данные любого типа, которые можно отсортировать в чётко определённом линейном порядке. Единственное его ограничение состоит в том, что размер записи в индексе не может превышать примерно треть страницы (после сжатия TOAST, если оно применяется).
Так как каждый класс операторов btree устанавливает порядок сортировки для своего типа данных, классы операторов btree (или, фактически, семейства операторов) оказались показательными и полезными для представления и понимания семантики сортировки в PostgreSQL. Как следствие, они приобрели некоторые возможности, которые выходят за рамки необходимого минимума для поддержки индексов btree и используются частями системы, довольно далёкими от методов доступа btree.
64.1.2. Поведение классов операторов B-дерева #
Как показано в Таблице 36.3, класс операторов btree должен предоставить пять операторов сравнения, <
, <=
, =
, >=
и >
. Хотя можно было ожидать, что частью этого класса будет и оператор <>
, но это не так, потому что использовать <>
в предложении WHERE для поиска по индексу практически бесполезно. (Для некоторых целей планировщик условно относит оператор <>
к классу операторов btree, но он находит данный оператор как отрицание оператора =
, а не обращаясь к pg_amop
.)
Когда несколько типов данных имеют практически одинаковую семантику сортировки, их классы операторов можно сгруппировать в семейство операторов. Это полезно тем, что позволяет планировщику делать выводы о межтиповых сравнениях. Каждый класс операторов в семействе должен содержать операторы для одного своего типа входных данных (и сопутствующие опорные функции), тогда как межтиповые операторы сравнения и опорные функции являются «слабо» связанными с семейством. В семейство рекомендуется включать полный набор межтиповых операторов, чтобы планировщик мог представить любые условия, которые он может вывести, используя транзитивность.
Семейство операторов btree должно удовлетворять нескольким базовым положениям:
Оператор
=
должен представлять отношение эквивалентности; то есть для всех отличных от NULL значенийA
,B
,C
определённого типа данных:A
=
A
— истина (рефлексивность)если
A
=
B
, тоB
=
A
(симметрия)если
A
=
B
иB
=
C
, тоA
=
C
(транзитивность)
Оператор
<
должен представлять отношение строгого упорядочивания; то есть для всех отличных от NULL значенийA
,B
,C
:A
<
A
— ложно (антирефлексивность)если
A
<
B
иB
<
C
, тоA
<
C
(транзитивность)
Более того, упорядочивание действует глобально; то есть для любых отличных от NULL значений
A
,B
:истинным является ровно одно из условий:
A
<
B
,A
=
B
илиB
<
A
(трихотомия)
(Разумеется, определение функции, осуществляющей сравнение, вытекает из закона трихотомии.)
Остальные три оператора определяются через операторы =
и <
очевидным образом и должны работать согласованно с последними.
Для семейства операторов, поддерживающего несколько типов данных, вышеперечисленные законы должны выполняться при значениях A
, B
, C
, относящихся к любым типам из семейства. Транзитивность обеспечить сложнее всего, так как в ситуациях с разными типами она требует согласованного поведения двух или трёх различных операторов. Так например, в одном семействе операторов не смогут работать типы float8
и numeric
, по крайней мере при текущем подходе, когда значения numeric
преобразуются во float8
для сравнения с float8
. Из-за ограниченной точности типа float8
различные значения numeric
могут оказаться равными одному значению float8
, что нарушит закон транзитивности.
Ещё одно требование для семейства, рассчитанного на несколько типов данных, состоит в том, что любое неявное или двоично-совместимое приведение, которое определено между типами, включёнными в семейство операторов, не должно менять соответствующий порядок сортировки.
Должно быть достаточно понятно, почему индекс-B-дерево требует выполнения этих законов для одного типа данных: без этого упорядочивание ключей невозможно. Кроме того, для поиска в индексе по ключу другого типа данных необходимо, чтобы значения двух типов сравнивались корректно. Расширение семейства до трёх или более типов данных не является обязательным для самого механизма индекса btree, но может быть полезным для планировщика в целях оптимизации.
64.1.3. Опорные функции B-деревьев #
Как показано в Таблице 36.9, btree определяет одну необходимую и четыре необязательных опорных функции. Таким образом, пользователь может задать пять методов:
order
Для всех комбинаций типов данных, для которых семейство операторов btree предоставляет операторы сравнения, оно должно предоставлять опорную функцию сравнения в
pg_amproc
с номером 1 и camproclefttype
/amprocrighttype
, равными левому и правому типу сравнения (то есть тем же типам данных, с которыми соответствующие операторы зарегистрированы вpg_amop
). Эта функция сравнения должна принимать два отличных от NULL значения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. Эти функции позволяют реализовывать сравнения для целей сортировки гораздо эффективнее, чем это возможно при прямолинейном вызове функции поддержки сравнения. Задействованные в этом программные интерфейсы определены в
src/include/utils/sortsupport.h
.in_range
Дополнительно семейство операторов btree может предоставить опорные функции in_range, которые регистрируются под номером 3. Они не используются в ходе операций с индексом btree; вместо этого они расширяют семантику семейства операторов, чтобы оно могло поддерживать оконные предложения
RANGE
смещение
PRECEDING
иRANGE
смещение
FOLLOWING
(см. Подраздел 4.2.8). По сути они предоставляют дополнительную информацию, позволяющую добавлять или вычитатьсмещение
в соответствии с порядком сортировки, принятым в семействе.Функция
in_range
должна иметь сигнатуруin_range(
значение
type1,база
type1,смещение
type2,вычитание
bool,меньше
bool) returns boolЗначение
ибаза
должны быть одного типа данных, и этот тип должен поддерживаться семейством операторов (то есть это должен быть тип, для которого реализуется сортировка). Однакосмещение
может быть другого типа, который никаким другим образом не поддерживается данным семейством. Например, встроенное семействоtime_ops
предоставляет функцию, для которойсмещение
имеет типinterval
. Семейство может предоставлять функцииin_range
для любых из своих поддерживаемых типов и одного или нескольких типовсмещений
. Каждая функцияin_range
должна регистрироваться вpg_amproc
с полемamproclefttype
, равнымtype1
, иamprocrighttype
, равнымtype2
.Суть действия функции
in_range
зависит от двух логических флагов. Она должна прибавить или вычесть избазы
смещение
, а затем сравнитьзначение
с результатом следующим образом:если
!
вычитание
и!
меньше
, возвращаетсязначение
>=
(база
+
смещение
)если
!
вычитание
именьше
, возвращаетсязначение
<=
(база
+
смещение
)если
вычитание
и!
меньше
, возвращаетсязначение
>=
(база
-
смещение
)если
вычитание
именьше
, возвращаетсязначение
<=
(база
-
смещение
)
Прежде чем делать это, функция должна проверить знак
смещения
и, если оно отрицательное, выдать ошибкуERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE
(22013) с текстом ошибки «invalid preceding or following size in window function» (неверная предшествующая или последующая величина в оконной функции). (Это требуется стандартом SQL, но нестандартные семейства операторов могут проигнорировать данное ограничение, так как оно не несёт большой смысловой нагрузки.) Проверка этого требования делегируется функцииin_range
, чтобы коду ядра не требовалось понимать, что означает «меньше нуля» для произвольного типа данных.Кроме того, функции
in_range
, если это практично, могут не выдавать ошибку, когда операциябаза
+
смещение
илибаза
-
смещение
приводит к переполнению. Правильный результат сравнения можно получить, даже если это значение выходит за границы допустимого диапазона этого типа данных. Заметьте, что если для типа данных определены такие понятия, как «бесконечность» и «NaN», могут потребоваться дополнительные меры для обеспечения согласованности результатовin_range
с обычным порядком сортировки данного семейства операторов.Результаты функции
in_range
должны соответствовать порядку сортировки, устанавливаемому семейством операторов. Точнее говоря, при любых фиксированных аргументахсмещение
ивычитание
справедливо:Если
in_range
сменьше
= true возвращает true для некоторогозначения1
ибазы
, true должно возвращаться для каждогозначения2
<=
значению1
с той жебазой
.Если
in_range
сменьше
= true возвращает false для некоторогозначения1
ибазы
, false должно возвращаться для любогозначения2
>=
значению1
с той жебазой
.Если
in_range
сменьше
= true возвращает true для некоторогозначения
ибазы1
, true должно возвращаться для каждойбазы2
>=
базе1
с тем жезначением
.Если
in_range
сменьше
= true возвращает false для некоторогозначения
ибазы1
, false должно возвращаться для любойбазы2
<=
базе1
с тем жезначением
.
Аналогичные утверждения с противоположными условиями должны выполняться при
меньше
= false.Если упорядочиваемый тип (
type1
) является сортируемым, функцииin_range
будет передан OID соответствующего правила сортировки через стандартный механизм PG_GET_COLLATION().Функции
in_range
не должны обрабатывать NULL в аргументах и обычно помечаются как строгие.equalimage
Дополнительно семейство операторов btree может предоставить опорные функции
equalimage
(«равенство подразумевает равенство образов»), регистрируемые под номером 4. Эти функции позволяют коду ядра определить, безопасно ли применять исключение дубликатов в B-дереве. В настоящее время функцииequalimage
вызываются только при построении или перестроении индекса.Функция
equalimage
должна иметь сигнатуруequalimage(
opcintype
oid
) returns boolЕё результатом будет статическая информация о классе операторов и правиле сортировки. Результат
true
означает, что функцияorder
для класса операторов будет возвращать0
(признак равенства аргументов), только когда аргументыA
иB
взаимозаменяемы без потери семантической информации. Если функцияequalimage
не определена или она возвращаетfalse
, рассчитывать на выполнение данного условия нельзя.В аргументе
opcintype
передаётся
типа данных, индексируемого данным классом операторов. Это сделано для удобства повторного использования нижележащей функцииpg_type
.oidequalimage
в разных классах операторов. Если типopcintype
поддерживает правила сортировки, функцииequalimage
будет передан OID соответствующего правила через стандартный механизмPG_GET_COLLATION()
.С точки зрения класса операторов возвращаемое значение
true
означает, что возможно безопасное применение исключения дубликатов (или оно безопасно для правила сортировки, OID которого был передан функцииequalimage
). Однако код ядра будет считать исключение дубликатов безопасным для индекса, только если для каждого столбца в этом индексе используется класс операторов, регистрирующий функциюequalimage
, и все эти функции при вызове возвращаютtrue
.Равенство образов почти равнозначно простому битовому равенству. Но есть одно небольшое различие: когда индексируется тип данных varlena, представление двух равных образов на диске может отличаться из-за различного применения сжатия TOAST к входным данным. Говоря формально, когда функция
equalimage
класса операторов возвращаетtrue
, можно полагать, что функция на Cdatum_image_eq()
гарантированно будет согласованной с функциейorder
класса операторов (при условии передачи обеим функциям одинакового OID правила сортировки).Код ядра в принципе не может сделать какие-то выводы о свойстве класса операторов «равенство подразумевает равенство образов» в семействе операторов для множества типов, анализируя другие классы операторов в том же семействе. Также не имеет смысла регистрировать межтиповую функцию
equalimage
для семейства операторов, и при попытке сделать это произойдёт ошибка. Это связано с тем, что свойство «равенство подразумевает равенство образов» зависит не только от семантики сортировки/равенства, определяемой в некоторой степени на уровне семейства операторов. Вообще говоря, это свойство относится к конкретному типу и должно рассматриваться отдельно.Для классов операторов, поставляемых в базовом продукте PostgreSQL, принято соглашение регистрировать универсальную функцию
equalimage
. Большинство классов операторов регистрируют в качестве такой функцииbtequalimage()
, которая устанавливает, что исключение дубликатов безопасно без дополнительных условий. Операторы классов для типов данных, поддерживающих правила сортировки, например, для типаtext
, регистрируют функциюbtvarstrequalimage()
, которая устанавливает, что исключение дубликатов безопасно с детерминированными правилами сортировки. Для сохранения порядка в сторонних расширениях также рекомендуется регистрировать их собственные функцииequalimage
.options
В дополнение семейство операторов btree может предоставить опорные функции
options
(«параметры класса операторов»), регистрируемые под номером 5. Эти функции позволяют определить набор видимых пользователю параметров, управляющих поведением класса операторов.Опорная функция
options
должна иметь сигнатуруoptions(
relopts
local_relopts *
) returns voidЭтой функции передаётся указатель на структуру
local_relopts
, в которую нужно внести набор параметров, относящихся к классу операторов. Обращаться к этим параметрам из других опорных функций можно с помощью макросовPG_HAS_OPCLASS_OPTIONS()
иPG_GET_OPCLASS_OPTIONS()
.В настоящее время опорная функция
options
не определена ни для одного из классов операторов btree. Сама организация B-дерева не позволяет гибко менять представление ключей, как это возможно с GiST, SP-GiST, GIN и BRIN. Поэтому с существующим методом доступа к индексу-B-дереву для функцииoptions
нет полезных применений. Тем не менее эта опорная функция была добавлена для B-дерева ради единообразия и не исключено, что она окажется полезной по мере развития реализации B-дерева в PostgreSQL.
64.1.4. Реализация #
В этом разделе освещаются детали реализации индекса-B-дерева, знание которых может быть полезно для специалистов. В дереве исходного кода имеется файл src/backend/access/nbtree/README
, в котором реализация B-дерева рассматривается ещё глубже, на уровне алгоритмов.
64.1.4.1. Структура B-дерева #
Индексы-B-деревья в PostgreSQL представляют собой многоуровневые иерархические структуры, в которых каждый уровень дерева может использоваться как двусвязный список страниц. Единственная метастраница индекса хранится в фиксированной позиции в начале первого файла сегмента индекса. Все остальные страницы делятся на внутренние и на листовые. Листовые страницы находятся на самом нижнем уровне дерева. Все более высокие уровни состоят из внутренних страниц. Листовая страница содержит кортежи, указывающие на строки в таблице, а внутренняя страница — кортежи, указывающие на следующий уровень в дереве. Обычно листовые страницы составляют около 99% всех страниц индекса. И для тех, и для других страниц используется один стандартный формат, описанный в Разделе 65.6.
Новые листовые страницы добавляются в B-дерево когда существующая листовая страница не может вместить новый поступающий кортеж. При этом выполняется операция разделения страницы, освобождающая место на переполнившейся странице, перенося подмножество изначально содержащихся на ней элементов на новую страницу. При разделении страницы в её родительскую страницу также должна быть добавлена ссылка вниз, что может потребовать произвести разделение и этой родительской страницы. Разделение страниц «каскадно поднимается вверх» рекурсивным образом. Когда же и корневая страница не может вместить новую ссылку вниз, производится операция разделения корневой страницы. При этом в структуру дерева добавляется новый уровень, на котором оказывается новая корневая страница, стоящая над той, что была корневой ранее.
64.1.4.2. Восходящее удаление индексных кортежей #
В реализации индексов-B-деревьев не учитывается, что в среде MVCC может быть несколько версий одной логической строки таблицы; для индекса каждый кортеж является независимым объектом, требующим отдельного элемента в индексе. Кортежи «отработанных версий» иногда могут накапливаться, что чревато задержками и замедлением при выполнении запросов. Это обычно происходит при нагрузке с преобладанием UPDATE
, когда для большинства отдельных операций изменения данных нельзя применить оптимизацию HOT. Изменение значения даже одного индексируемого столбца во время UPDATE
всегда требует добавления нового набора индексных кортежей — отдельного кортежа для каждого индекса в таблице. Более того, обратите внимание, что новые кортежи требуются и для тех индексов, которые не были «логически изменены» командой UPDATE
. Во всех индексах будут нужны дополнительные физические кортежи, указывающие на последнюю версию строки в таблице. Каждый новый кортеж в каждом индексе, как правило, должен сосуществовать с исходным подвергшимся изменению кортежем в течение короткого периода времени (обычно недолго после фиксации транзакции UPDATE
).
В индексах-B-деревьях постепенно удаляются кортежи отработанных версий в ходе процедуры восходящего удаления индексных кортежей. Каждый проход процедуры удаления вызывается, когда ожидается, что произойдёт «разделение страниц из-за отрабатывания версий». Это касается только тех индексов, которые не были логически изменены операторами UPDATE
, так как именно в них возможно концентрированное накопление устаревших версий на определённых страницах. Реализация обычно старается избежать разделения страниц, хотя вполне возможно, что определённые решения на её уровне не позволят идентифицировать и удалить ни одного мусорного кортежа в индексе (в этом случае проблема размещения нового кортежа на заполненной листовой странице решается путём разделения страницы или в результате исключения дубликатов). Большое количество версий каждой отдельной логической строки, которое нужно прочитать при каждом сканировании индекса, является важным отрицательным фактором общей производительности и скорости отклика системы. Процедура восходящего удаления индексных кортежей выбирает на листовой странице предположительно мусорные кортежи на основании качественных характеристик, определяемых логическими строками и версиями. Это отличает её от «нисходящей» уборки индекса, выполняемой процессами автоочистки, которая запускается при превышении определённых количественных пороговых значений на уровне таблицы (см. Подраздел 24.1.6).
Примечание
Не все операции удаления, выполняемые в индексах-B-деревьях, реализуются процедурой восходящего удаления. Существует другая категория таких операций: простое удаление индексных кортежей. Это отложенная операция обслуживания, удаляющая индексные кортежи, о которых известно, что их можно безопасно стереть (то есть те, для идентификаторов которых установлен бит LP_DEAD
). Как и восходящее удаление индексных кортежей, простое удаление происходит в тот момент, когда ожидается разделение страницы, для предотвращения этого разделения.
Простое удаление выполняется по возможности в том смысле, что оно может произойти только при условии, что в результате недавних сканирований индекса были установлены биты LP_DEAD
для обработанных элементов. До PostgreSQL 14 единственной категорией операций удаления в B-дереве было простое удаление. Основное различие между простым и восходящим удалением заключается в том, что только первое обусловлено активностью сканирований индекса, а второе ориентировано именно на активность отрабатывания версий, вызываемую командами UPDATE
, которые не изменяют логически индексированные столбцы.
При определённой нагрузке восходящее удаление индексных кортежей выполняет основную работу по уборке мусорных кортежей из индексов. Такой эффект ожидается для любого индекса-B-дерева, который в значительной мере затрагивается активностью отрабатывания версий из-за команд UPDATE
, почти никогда не изменяющих логически столбцы, покрываемые индексом. Среднее и наибольшее количество версий для логической строки может поддерживаться на низком уровне исключительно за счёт постоянных точечных проходов удаления. Вполне возможно, что размер определённых индексов на диске никогда не увеличится ни на одну страницу/блок, несмотря на постоянное отрабатывание версий, вызываемое командами UPDATE
. Но даже в этом случае в конце концов потребуется полная «зачистка» индекса процедурой VACUUM
(обычно запускаемой в рабочем процессе автоочистки) как часть совместной уборки таблицы и всех её индексов.
В отличие от VACUUM
, восходящее удаление не даёт никаких надёжных гарантий относительно возраста старейшего мусорного индексного кортежа. Ни в одном индексе не допускается сохранение «плавающих мусорных» кортежей, ставших мёртвыми до консервативной точки отсечения, являющейся общей для таблицы и всех её индексов. Этот фундаментальный инвариант на уровне таблицы позволяет обеспечивать безопасную циркуляцию табличных идентификаторов (TID). Именно таким образом разные логические строки могут повторно использовать один и тот же идентификатор с течением времени (хотя это невозможно для двух логических строк, время жизни которых укладывается в один и тот же цикл VACUUM
).
64.1.4.3. Исключение дубликатов #
Дубликатом называется кортеж на листовой странице (кортеж, указывающий на строку таблицы), у которого все ключевые столбцы индекса имеют значения, соответствующие значениям столбцов из как минимум одного другого кортежа на листовой странице в том же индексе. Дублирующиеся кортежи довольно часто встречаются на практике. В индексах-B-деревьях такие дубликаты могут представляться особым экономичным образом при включении дополнительного механизма — исключения дубликатов.
Работа этого механизма заключается в периодическом объединении групп дублирующихся кортежей и формировании одного кортежа со списком идентификаторов для каждой группы. В таком представлении значения ключевых столбцов хранятся в единственном экземпляре, а за ними идёт отсортированный массив идентификаторов TID, указывающих на строки в таблице. Это существенно уменьшает размер хранящихся индексов, в которых каждое значение (или каждое уникальное сочетание значений столбцов) появляется в среднем несколько раз. В результате может значительно увеличиться скорость выполнения запросов, а также могут сократиться издержки, связанные с регулярной очисткой индексов.
Примечание
Исключение дубликатов в B-дереве работает эффективно и с «дубликатами», содержащими значение NULL, несмотря на то, что значения NULL не считаются равными между собой согласно операторам =
, входящим в классы операторов btree. Это объясняется тем, что с точки зрения реализации, работающей с внутренним представлением структуры B-дерева, NULL является просто одним из элементов множества всех возможных значений в индексе.
Процесс исключения дубликатов осуществляется по необходимости, когда вставляется новый элемент, не умещающийся на существующей листовой странице, но только тогда, когда удаление индексного кортежа не может освободить достаточно места для нового элемента (обычно удаление недолго рассматривается, а затем пропускается). В отличие от кортежей со списками идентификаторов GIN, в B-дереве эти кортежи не должны расширяться при каждом добавлении нового дубликата; они просто образуют другое физическое представление исходного логического содержимого листовой страницы. При таком подходе обеспечивается стабильная производительность при смешанной нагрузке чтения-записи. Исключение дубликатов должно дать как минимум заметное увеличение производительности для большинства клиентских приложений. По умолчанию оно включено.
Команды CREATE INDEX
и REINDEX
также выполняют исключение дубликатов, создавая кортежи со списками идентификаторов, но применяют несколько другую стратегию. Каждая группа дублирующихся обычных кортежей, обнаруженных в отсортированных данных, преобразуется в кортеж со списком идентификаторов до того, как данные добавляются в текущую листовую страницу. При этом в каждый такой кортеж упаковывается как можно больше идентификаторов (TID). После этого листовые страницы записываются обычным способом, без дополнительного прохода для исключения дубликатов. Эта стратегия подходит для команд CREATE INDEX
и REINDEX
, так как они обрабатывают все данные сразу.
Если же в профиле нагрузки преобладает запись и исключение дубликатов не приносит выигрыша ввиду отсутствия или небольшого числа дублирующихся значений, этот механизм может породить небольшие постоянные издержки (если он не отключён). В таких случаях его можно отключить для отдельных индексов с помощью параметра хранения deduplicate_items
. При нагрузке только на чтение никакие дополнительные издержки не возникают, так как кортежи со списком идентификаторов читаются так же эффективно, как и кортежи в стандартном представлении. Поэтому чаще всего отключение этого механизма не будет полезным.
Исключение дубликатов иногда может использоваться для уникальных индексов (а также уникальных ограничений). Благодаря этому дополнительные дубликаты отработанных версий могут временно «поглощаться» листовыми страницами. Исключение дубликатов в уникальных индексах дополняет восходящее удаление индексных кортежей, особенно в тех случаях, когда длительная транзакция держит снимок, препятствующий сборке мусора. Тем самым выигрывается время для восстановления эффективности стратегии восходящего удаления индексных кортежей. Откладывание разделения страниц до естественного завершения одной длительной транзакции позволяет успешно произвести проход восходящего удаления, который ранее был невозможен.
Подсказка
Для определения необходимости провести процедуру исключения дубликатов в уникальном индексе применяются дополнительные соображения. В таких индексах как правило можно перейти сразу к разделению листовой страницы, не расходуя лишние циклы на бесполезные проходы в поиске дубликатов. Если вас беспокоят возможные издержки, которые могут быть связаны с исключением дубликатов, вы можете установить значение deduplicate_items = off
для отдельных индексов. Однако его вполне можно оставить включённым и для уникальных индексов.
Исключение дубликатов может применяться не всегда ввиду ограничений на уровне реализации. Возможность его применения определяется во время выполнения CREATE INDEX
или REINDEX
.
Учтите, что исключение дубликатов считается небезопасным и не может применяться в следующих случаях, когда возможны семантические различия равных значений:
Исключение дубликатов не может применяться с типами
text
,varchar
иchar
в случае использования недетерминированных правил сортировки, так как в равных значениях должны сохраняться возможные различия в регистре и диакритических знаках.Исключение дубликатов невозможно с типом
numeric
, так как для равных значений должен сохраняться числовой масштаб, который может быть разным.Исключение дубликатов не может применяться с типом
jsonb
, так как внутри класса операторов B-дереваjsonb
используется типnumeric
.Исключение дубликатов невозможно для типов
float4
иfloat8
. В этих типах имеются разные представления значений-0
и0
, которые при этом считаются равными. Однако отличие между ними должно сохраняться.
Имеется ещё одно ограничение на уровне реализации, которое может быть снято в будущих версиях PostgreSQL:
Исключение дубликатов невозможно с типами-контейнерами (это составные, диапазонные типы, а также массивы).
Есть ещё одно ограничение на уровне реализации, действующее вне зависимости от применяемого класса операторов или правила сортировки:
Исключение дубликатов не может применяться в индексах с
INCLUDE
.