Время прочтения: 5 мин.

Введение

При формировании запросов с составным индексом нужно обращать внимание на логику выстраивания технической схемы поиска так же, как и в случае работы с обычными полями. Забегая вперёд, нужно сразу отметить, что полноценное и корректное использование составного индекса будет реализовано только в случае запроса по стартовым позициям таблицы, т.е. самым верхним/левым столбцам индекса.

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

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

Сам скрипт будет выглядеть следующим образом:

USE	TB70_SANDBOX
GO

--Создаём тестовую таблицу
CREATE TABLE dvb.CompositIndex_TEST ( --"dvb" - схема, "CompositIndex_TEST" - Название таблицы
	Id		INT IDENTITY(1,1) NOT NULL,
	NAME0		VARCHAR(50), --столбец с именем "NAME" лучше не использовать =) !По тексту буду называть полем "NAME"
	SURNAME	VARCHAR(50),
	Mobile		VARCHAR(10),
	Email		VARCHAR(50),
	DateUpdate	DATETIME
)
GO
--Заливаем в таблицу 1млн. рандомных записей. 
--Они нужны нам только для реалистичности поиска, не более
INSERT INTO dvb.CompositIndex_TEST (NAME0, SURNAME, Mobile, Email, DateUpdate)
SELECT TOP 1000000 REPLACE(NEWID(),'-',''), REPLACE(NEWID(),'-',''), 
	CAST( CAST(ROUND(RAND(CHECKSUM(NEWID()))*1000000000+4000000000,0) AS BIGINT) AS VARCHAR(10)),
	REPLACE(NEWID(),'-','') + '@gmail.com',     
	DATEADD(HOUR,CAST(RAND(CHECKSUM(NEWID())) * 19999 as INT) + 1 ,'2006-01-01')
FROM sys.all_columns c1
        CROSS JOIN sys.all_columns c2
GO
--Добавляем ещё одну запись, которую мы и будем использовать для эксперимента
UPDATE dvb.CompositIndex_TEST 
SET	NAME0 = 'Ivan',
	SURNAME = 'Petrov',
	Mobile = '9246402424',
	Email = 'ivan_petrov@gmail.com'
WHERE	Id = 100000
GO
--Создаём Первичный ключ (Primary Key/PK) и Кластерный индекс (Clustered Index/CI) для столбца "Id"
ALTER TABLE dvb.CompositIndex_TEST 
ADD CONSTRAINT PK_Customers_Id 
PRIMARY KEY CLUSTERED (Id)
GO

Реализация составного некластеризованного индекса для столбцов NAME и SURNAME

Для изучения подходов поиска, создадим некластеризованный индекс в столбцах NAME и SURNAME, по скрипту ниже:

--Создадим некластеризованный индекс для столбцов NAME0 и SURNAME
CREATE NONCLUSTERED INDEX IX_Customers_NAME0_SURNAME
	ON dvb.CompositIndex_TEST (NAME0, SURNAME)
GO

Заранее включаем в Sql Server Management Studio план выполнения, либо на панели инструментов, либо сочетанием Ctrl + L.

Настало время заняться экспериментом и узнать, как порядок столбцов в приведенном выше составном индексе влияет на его использование для запросов к этой таблице.

Пример №1: Порядок столбцов NAME и SURNAME как в таблице

Выполняем запрос

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST WITH (nolock)
WHERE NAME0 = 'Ivan' AND SURNAME = 'Petrov'

И смотрим план выполнения запроса

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

Пример №2: Обратный порядок столбцов. Сначала SURNAME, затем NAME

Выполняем запрос

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST
WHERE SURNAME = 'Petrov' AND NAME0 = 'Ivan'

Сверяемся с планом выполнения

Результат ничем не отличается от 1-ого примера. Хорошо, двигаемся дальше…

Пример №3: Запрос только по столбцу NAME

Выполняем запрос

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST WITH (nolock)
WHERE NAME0 = 'Ivan'

И что же получилось?

Запрос по одному из столбцов составного индекса тоже не повлиял на производительность запроса. Теперь попробуем выполнить запрос по второму столбцу составного индекса и будем подводить итоги.

Пример №4: Запрос только по столбцу SURNAME

Выполняем запрос

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST WITH (nolock)
WHERE SURNAME = 'Petrov'

Уже по привычке смотрим, что получилось в плане выполнения:

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

ИТОГИ: Проверяем стоимости всех запросов

Для наглядности выполним все 4 запроса из примеров в одном

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST WITH (nolock)
WHERE NAME0 = 'Ivan' AND SURNAME = 'Petrov'

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST
WHERE SURNAME = 'Petrov' AND NAME0 = 'Ivan'

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST WITH (nolock)
WHERE NAME0 = 'Ivan'

SELECT Id, NAME0, SURNAME
FROM dvb.CompositIndex_TEST WITH (nolock)
WHERE SURNAME = 'Petrov'

Казалось бы, какая разница!? Но она есть и достаточно ощутимая. Время сделать простой вывод.

Вывод:

Опытным путём удалось разобраться, как Sql Server интерпретирует запрос в составном индексе. Эталонная отработка запроса будет возможна только в том случае, если при сокращении количества индексов в запросе будут присутствовать ведущие столбцы, а именно стоящие в левом положении по отношению к остальным. Это важно учитывать, когда столбцы содержат не Имя и Фамилию (как в нашем примере) и изначально занимают стартовую позицию, а участвуют, как ключ, после объединения нескольких СУБД или, ввиду других исторических особенностей используются столбцы, «раскиданные» по таблице.

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