В чем разница первичного ключа и кластерного индекса в SQL Server?
Что проверяют
Различаете ли вы логическое ограничение и способ физической организации таблицы и знаете ли, почему в SQL Server их постоянно путают. Следом обычно спрашивают, что выбрать кластерным ключом и чем плох uniqueidentifier со случайными значениями.
Как отвечать
Разведите понятия и покажите, где они пересекаются:
- первичный ключ: ограничение целостности. Значения уникальны,
NULLзапрещён, ключ в таблице один, но может быть составным. На него ссылаются внешние ключи. Для проверки уникальности SQL Server всегда создаёт под ключом индекс; - кластерный индекс: способ хранения самой таблицы. Это B-дерево, на листовом уровне которого лежат строки данных, упорядоченные по ключу. Он один на таблицу, а таблица без него называется кучей (heap);
- связь по умолчанию:
PRIMARY KEYсоздаёт кластерный индекс, если его в таблице ещё нет. Это поведение по умолчанию, а не правило: можно объявитьPRIMARY KEY NONCLUSTEREDи сделать кластерным другой столбец; - некластерные индексы: в качестве указателя на строку хранят кластерный ключ, поэтому широкий кластерный ключ раздувает все остальные индексы таблицы;
- неуникальный кластерный индекс: допустим, SQL Server сам добавит к дубликатам скрытый 4-байтовый uniquifier.
Хороший кластерный ключ узкий, уникальный, неизменяемый и растущий: int или bigint с IDENTITY. Со стороны C#: в миграциях EF Core первичный ключ по умолчанию получается кластерным, отключить это можно через HasKey(...).IsClustered(false) в Fluent API. Для ключей Guid провайдер SQL Server по умолчанию генерирует последовательные значения на клиенте, что снимает часть проблемы.
Где спотыкаются
Говорят, что первичный ключ и кластерный индекс одно и то же. Они совпадают только из-за поведения по умолчанию.
Утверждают, что строки физически лежат на диске строго по порядку. Порядок логический: страницы связаны в список, а после разбиений физически разбросаны, отсюда фрагментация.
Делают кластерным ключом Guid из NEWID(). Вставки идут в случайные страницы, начинаются разбиения и фрагментация. Если Guid нужен, берут NEWSEQUENTIALID() или оставляют такой ключ некластерным.