Стоит ли отмечать составной индекс как уникальный, если он содержит первичный ключ?

9

Дана некоторая таблица с первичным ключом, например:

CREATE TABLE Customers (
   CustomerID int NOT NULL PRIMARY KEY,
   FirstName nvarchar(50),
   LastName nvarchar(50),
   Address nvarchar(200),
   Email nvarchar(260)
   --...
)

у нас есть уникальный первичный ключ CustomerID.

Традиционно мне могут понадобиться некоторые дополнительные индексы покрытия; например, чтобы быстро найти пользователя с помощью CustomerIDили Email:

CREATE INDEX IX_Customers_CustomerIDEmail ON Customers
(
   CustomerID,
   Email
)

И это те виды индексов, которые я создавал десятилетиями.

Не обязательно быть уникальным, но на самом деле это

Сам индекс существует, чтобы избежать сканирования таблицы; это вспомогательный индекс для повышения производительности (индекс не является ограничением для обеспечения уникальности).

Сегодня я вспомнил немного информации - SQL Server может использовать тот факт, что:

  • столбец имеет ограничение внешнего ключа
  • столбец имеет уникальный индекс
  • ограничение является доверенным

чтобы помочь ему оптимизировать выполнение запроса. Фактически, из Руководства по проектированию индексов SQL Server :

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

Учитывая, что мой многостолбцовый индекс содержит первичный ключ, этот составной индекс будет де-факто уникальным. Это не ограничение, которое мне особенно нужно, чтобы SQL Server применял каждый раз при вставке или обновлении; но дело в том , что это не-кластерный индекс является уникальным.

Есть ли какое-либо преимущество в маркировке этого фактического уникального индекса как уникального?

СОЗДАТЬ УНИКАЛЬНЫЙ ИНДЕКС IX_Customers_CustomerIDEmail ON Клиенты
(
   Пользовательский ИД,
   Электронное письмо
)

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

  • Но, возможно, он этого не знает, и для оптимизатора есть преимущество, если я все равно объявлю индекс уникальным.
  • За исключением, возможно, это теперь может привести к замедлению во время вставок и обновлений, где он должен выполнять проверки уникальности - там, где раньше это никогда не было раньше.
  • Если он не знает, что индекс гарантированно уже будет уникальным, потому что он содержит первичный ключ.

Я не могу найти никаких рекомендаций от Microsoft о том, что делать, если составной индекс содержит первичный ключ.

Преимущества уникальных индексов включают следующее:

  • Целостность данных определенных столбцов обеспечивается.
  • Предоставляется дополнительная информация, полезная для оптимизатора запросов.

Должен ли я пометить составной индекс как уникальный, если он уже содержит первичный ключ? Или SQL Server может выяснить это для себя?

Ян Бойд
источник
4
Чтобы быстро найти клиента по CustomerID, вероятно, достаточно PK. Чтобы найти клиента по электронной почте, я предпочел бы иметь второй индекс только по электронной почте (он уже охватывает ключ кластеризации). В любом случае, я знаю, что это вымышленное, но да, я бы отметил его как уникальное, если вы знаете, что оно должно быть уникальным, и это ограничение, вероятно, не повредит вставкам настолько, чтобы компенсировать случаи, когда это может помочь оптимизатору. Но это все зависит от вашей рабочей нагрузки, о которой мы должны были бы спекулировать ...
Аарон Бертран
1
Мы обсудили это подробно здесь. И хотя мы не можем придумать какой-либо способ придумать какие-либо данные, чтобы так или иначе доказать это, мы предполагаем, что ребята из SQL Server, вероятно, очень умны, и оптимизатор, вероятно, уже способен выполнить эту мета-оптимизацию. Мы также считаем, что, вероятно, лучше всего, чтобы индекс сообщал о намерении - который является просто индексом, а не навязывал уникальность как бизнес-правило, когда в противном случае это было бы сделано только для того, чтобы попытаться угадать возможности SQL Server.
Ян Бойд

Ответы:

11

Стоит ли отмечать составной индекс как уникальный, если он уже содержит первичный ключ?

Возможно нет. Как бы то ни было, оптимизатор в любом случае может использовать информацию об уникальности содержимого ключевого столбца, поэтому реального преимущества нет.

Существует также важное последствие маркировки индекса уникальным в планах обновлений, которые модифицируют ключи этого индекса, чтобы учитывать:

Настроить

CREATE TABLE dbo.Customers 
(
   CustomerID int NOT NULL PRIMARY KEY,
   FirstName nvarchar(50),
   LastName nvarchar(50),
   [Address] nvarchar(200),
   Email nvarchar(260)
);

CREATE NONCLUSTERED INDEX 
    IX_Customers_CustomerIDEmail 
ON dbo.Customers
(
   CustomerID,
   Email
);

-- Pretend we have some rows
UPDATE STATISTICS dbo.Customers 
WITH ROWCOUNT = 100000, PAGECOUNT = 20000;

План обновления по индексу (неуникальный индекс)

UPDATE dbo.Customers 
SET Email = N'New', [Address] = 'New Address'
WHERE Email = N'Old' 
OPTION (QUERYTRACEON 8790); -- Per-index update plan

План выполнения:

Сплит и фильтр

Оптимизатор часто принимает решение на основе стоимости между обновлением некластеризованных индексов для каждой строки («узкий» план) или для индекса («широкий» план). Стратегия по умолчанию (за исключением таблиц OLTP в памяти) - это широкий план.

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

Дополнительная информация: Оптимизация запросов T-SQL, которые изменяют данные

В этом случае я использовал недокументированный флаг трассировки 8790, чтобы вызвать широкий план обновления: поэтому план показывает, что кластеризованные и некластеризованные индексы поддерживаются раздельно.

Разделение превращает каждое обновление в отдельную пару удаления и вставки; Фильтр отфильтровывает любые строки, которые не приведут к изменению индекса.

Дополнительная информация: ( Не обновляет обновления ) командой SQL Server QO.

План обновления по индексу (уникальный индекс)

-- Same index, but unique
CREATE UNIQUE INDEX IX_Customers_CustomerIDEmail ON Customers
(
   CustomerID,
   Email
)
WITH (DROP_EXISTING = ON);

UPDATE dbo.Customers 
SET Email = N'New', [Address] = 'New Address'
WHERE Email = N'Old' 
OPTION (QUERYTRACEON 8790); -- Per-index update plan

План выполнения:

Сплит-Sort-Collapse

Обратите внимание на дополнительные операторы Sort и Collapse, когда индекс помечен как уникальный.

Этот шаблон Split-Sort-Collapse требуется при обновлении ключей уникального индекса, чтобы предотвратить промежуточные нарушения уникального ключа.

Больше информации: Ведение уникальных индексов Крейг Фридман

Сортировка в частности может быть проблемой. Это не только ненужные дополнительные расходы, но и возможные потери на диске, если оценки неточны.

О некластеризованных ключах

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

Как следствие, следующее определение индекса:

CREATE INDEX IX_Customers_CustomerIDEmail ON Customers
(
   Email
)
WITH (DROP_EXISTING = ON);

... фактически содержит ключи (Email, CustomerID) на всех уровнях. Поэтому он «доступен» для обеих колонок:

SELECT * 
FROM dbo.Customers AS C WITH (INDEX(IX_Customers_CustomerIDEmail))
WHERE C.Email = N'Email'
AND C.CustomerID = 1;

стремится

Дополнительная информация: Подробнее о ключах некластеризованного индекса от Kalen Delaney

Пол Уайт 9
источник
8

SQL знает, что он уже уникален (если он включает в себя PK, он не может стать более уникальным), независимо от того, указали ли вы это явно.

Большая разница между неуникальным индексом и уникальным индексом заключается в том, что для неуникальных индексов требуется ключ кластеризованного индекса (со значением uniquifier, если CIX не объявлен как уникальный) на более высоких уровнях индекса, а не только на листе. уровень.

В вашем случае у вас уже есть CIX в ключе, что означает, что он будет уже на каждом уровне индекса.

Но вы можете создать таблицу, которая имеет отдельный PK (уникальный) и CIX (не имеет значения). Затем создайте неуникальный индекс, который включает PK в свой ключ. Поместите в таблицу несколько строк, в том числе несколько легко доступных значений varchar для вашего столбца CIX. Поместите достаточно строк, чтобы вызвать несколько уровней ваших индексов. Затем вы можете использовать DBCC IND, чтобы найти страницы в NCIX, и DBCC PAGE, чтобы открыть некоторые из них, чтобы посмотреть на данные, чтобы увидеть, находятся ли значения ключей CIX на более высоких уровнях.

Роб Фарли
источник