Основные соображения
Я вижу одно важное преимущество для куч и одно для кластерных таблиц, а также третье соображение, которое может пойти в любом случае.
Куча экономит вам слой косвенности. Индексы содержат идентификаторы строк, указывающие непосредственно (ну, не совсем, но как можно более прямо) на местоположение диска. Таким образом, поиск индекса по куче должен стоить примерно половину некластеризованного поиска по кластерной таблице.
Кластерный индекс сортируется, как таковой, благодаря (почти) свободному индексу. Поскольку индекс кластеризации отражается в физическом порядке данных, он занимает относительно мало места поверх самих фактических данных, которые, конечно же, нужно хранить в любом случае. Поскольку он физически упорядочен, сканирование диапазона по этому индексу может искать начальную точку, а затем очень эффективно перемещаться к конечной точке.
Индексы на кучах ссылаются на RID, которые являются 64-битными. Как уже упоминалось, некластеризованные индексы в кластеризованной таблице ссылаются на ключ кластеризации, который может быть меньше (32-битный INT
), таким же (64-битный BIGINT
) или больше (48-битный DATETIME2()
плюс 32-битный INT
, или 128-битный GUID). Очевидно, что более широкие ссылки делают для более крупных и более дорогих индексов.
Требования к пространству
С этими двумя таблицами:
CREATE TABLE TmpClustered
(
ID1 INT NOT NULL,
ID2 INT NOT NULL
)
ALTER TABLE TmpClustered ADD CONSTRAINT PK_Tmp1 PRIMARY KEY CLUSTERED (ID1)
CREATE UNIQUE INDEX UQ_Tmp1 ON TmpClustered (ID2)
CREATE TABLE TmpNonClustered
(
ID1 INT NOT NULL,
ID2 INT NOT NULL
)
ALTER TABLE TmpNonClustered ADD CONSTRAINT PK_Tmp2 PRIMARY KEY NONCLUSTERED (ID1)
CREATE UNIQUE INDEX UQ_Tmp2 ON TmpNonClustered (ID2)
... каждая из которых заполнена 8,7 М записями, для данных обоих требовалось 150 МБ; 120 МБ для индексов кластеризованной таблицы, 310 МБ для индексов некластеризованной таблицы. Это отражает тот факт, что кластеризованный индекс является более узким, чем RID, и что кластеризованный индекс в основном является «халявой». Без уникальных индексов ID2
необходимое пространство индекса уменьшается до 155 МБ для некластеризованной таблицы (наполовину, как и следовало ожидать), но только до 150 КБ. для кластеризованного ПК - почти ничего.
Таким образом, некластеризованный индекс 32-битного поля в кластерной таблице с 32-битным индексом (всего 64 бита, номинально) занял 120 МБ, а индекс 32-битного поля в куче с 64-битным RID (всего 96 бит, номинально) занял 155 МБ, что немного меньше, чем увеличение на 50%, которое наивно ожидалось бы при переходе с 64-битных на 96-битные ключи, но, конечно, есть издержки, которые уменьшают эффективную разницу в размере.
Заполнение двух таблиц и создание их индексов заняло одинаковое количество времени для каждой таблицы. Выполняя простые тесты, включающие сканирование или поиск, я не обнаружил существенных различий в производительности между таблицами, что соответствует официальному документу Microsoft, с которым gbn тщательно связан. Упомянутая бумага показывает значительную разницу для одновременного доступа; Я не уверен, почему это происходит, надеюсь, кто-то с большим опытом, чем я с большими объемами OLTP-систем, может сказать нам.
Добавление ~ 40 байтов случайных данных переменной длины существенно не изменило эту эквивалентность. Замена INT
s широкими UUID также не выполнялась (каждая таблица была замедлена примерно в одинаковой степени). Ваш пробег может отличаться, но в большинстве случаев важнее то, доступен ли индекс.
Остатки
Выполнение сканирования диапазона по некластеризованному индексу - либо потому, что таблица представляет собой кучу, либо индекс не является кластеризованным индексом - включает в себя сканирование индекса, а затем поиск по таблице для каждого попадания. Это может быть очень дорого, поэтому иногда дешевле просто отсканировать таблицу. Однако вы можете обойти это с помощью индекса покрытия. Это относится независимо от того, кластеризовали ли вы свою таблицу или нет.
Как отметил @gbn, простого способа сжатия кучи не существует. Однако, если ваша таблица постепенно увеличивается со временем - очень распространенный случай - будет мало потерь, так как пространство, освобожденное удалениями, будет заполнено новыми данными.
Некоторые из обсуждений кучи против кластерной таблицы, которые я видел, приводят любопытный аргумент, что куча без индексов уступает кластерной таблице в том, что она всегда требует сканирования таблицы. Это, конечно, верно, но более значимое сравнение - «большая хорошо проиндексированная кластерная таблица» и «большая хорошо проиндексированная куча». Если ваша таблица очень мала или вы всегда будете выполнять сканирование таблицы, то не имеет большого значения, кластеризуете ее или нет.
Поскольку каждый индекс в кластеризованной таблице ссылается на индекс кластеризации, фактически они охватывают все индексы. Запрос, который ссылается на индексированный столбец и кластерный столбец (столбцы), может выполнять сканирование индекса без поиска таблиц. Как правило, это не имеет значения, если ваш кластеризованный индекс является синтетическим ключом, но если это бизнес-ключ, который вам все равно нужно получить, это хорошая функция.
TL; DR
Я парень из хранилища данных, а не эксперт по OLTP. Для таблиц фактов я почти всегда использую индекс кластеризации для поля, который, скорее всего, потребует сканирования диапазона, обычно поля даты. Для таблиц измерений я кластеризируюсь на ПК, поэтому он предварительно сортируется для объединений с таблицами фактов.
Есть несколько причин для использования индексов кластеризации, но если ни одна из этих причин не применима, тогда издержки могут не стоить. Я подозреваю, что есть много «мы всегда так делали» и «это просто лучшая практика» для людей, использующих кластерные индексы повсеместно. Попробуйте оба с вашими данными и вашей нагрузкой и посмотреть , что работает лучше всего.