Создание соответствующего индекса для часто используемого запроса в SQL Server - PullRequest
7 голосов
/ 13 июня 2010

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

WHERE FieldA = @P1 AND (FieldB = @P2 OR FieldC = @P2)

и

WHERE FieldA = @P1 AND FieldB = @P2

P1 и P2 - параметры, вводимые в пользовательском интерфейсе или поступающие из внешних источников данных.

  • Поле A является int и крайне неуникальным, что означает: только два, три, четыре различных значения в таблице, скажем, 20000 строк
  • Поле B представляет собой varchar(20) и«почти» уникально, будет только очень мало строк, в которых FieldB может иметь то же значение
  • FieldC varchar(15) и также сильно различаться, но не так сильно, как FieldB
  • FieldA иFieldB вместе являются уникальными (но не образуют мой первичный ключ, который представляет собой простой автоматически увеличивающийся столбец идентификаторов с кластеризованным индексом)

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

FieldB (or better FieldC here?)
FieldC (or better FieldB here?)
FieldA

... или, что лучше, двух индексов:

FieldB
FieldA

и

FieldC
FieldA

Или есть даже другиеа лучшие варианты?Какой лучший способ и почему?

Спасибо за предложения заранее!

Редактировать:

Так же, как информация для других читателей: Здесь былодругой ответ, который был удален сейчас.На самом деле ответ показался мне очень полезным.Рекомендовалось создать два индекса (в соответствии с моим вторым вариантом выше) и переформулировать первый запрос, используя UNION из двух операторов выбора (один с WHERE FieldA = @P1 AND FieldB = @P2 и один с WHERE FieldA = @P1 AND FieldC = @P2) вместо OR дляизвлечь выгоду из обоих индексов (что не было бы в случае оператора OR).

Edit2:

Утверждение, что с OR индексы не используются ито, что UNION предпочтительнее, кажется неправильным - по крайней мере, согласно моим собственным тестам (см. мой собственный ответ ниже).

Ответы [ 2 ]

3 голосов
/ 13 июня 2010

Расширение Remus '(редактировать: теперь удалено) ответ ...

  • если @ p2 - это varchar (15), то вы не можете надежно сравнить с FieldB, это varchar (20)
  • если @ p2 равен varchar (20), тогда FieldC будет преобразован в varchar (20) и не будет использовать индекс (или в лучшем случае его сканировать)
  • если @ p1 имеет только 2, 3, 4 значения, то почему бы не tinyint и не уменьшить размер таблицы / индекса?

Я бы не стал беспокоиться об индексах, пока вы не решите эту проблему с приоритетом типа данных: это в первую очередь проблема предложения OR.

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

Я бы перевернул индексы из ответа Ремуса на FieldB, FieldA (и уникальный) и FieldC, FieldA из-за селективности FieldA

Редактировать после комментариев: нельзя сравнивать использование @ p2 с использованием константных строк.

0 голосов
/ 29 июня 2010

Я добавляю свой собственный ответ после некоторых тестов с большой базой данных (в SQL Server 2008):

Во-первых, я выбрал второй вариант, то есть я создал два индекса:

CREATE UNIQUE NONCLUSTERED INDEX [IX_B] ON [dbo].[MyTable] 
(
    [FieldB] ASC,
    [FieldA] ASC
)
CREATE NONCLUSTERED INDEX [IX_C] ON [dbo].[MyTable] 
(
    [FieldC] ASC,
    [FieldA] ASC
)

Я проверил два запроса:

declare @p1 int = 1;
declare @p2 varchar(20) = '12345678';

select * from MyTable
where FieldA=@p1 and (FieldB=@p2 or FieldC=@p2);

При выполнении этого запроса я получаю следующий план запроса (ID - это первичный ключ таблицы, PK_MyTable кластеризованный индекс напервичный ключ):

|--Nested Loops(Inner Join, OUTER REFERENCES:([MyDb].[dbo].[MyTable].[ID]))
   |--Stream Aggregate(GROUP BY:([MyDb].[dbo].[MyTable].[ID]) DEFINE:([MyDb].[dbo].[MyTable].[FieldA]=ANY([MyDb].[dbo].[MyTable].[FieldA])))
   |  |--Merge Join(Concatenation)
   |     |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_B]), SEEK:([MyDb].[dbo].[MyTable].[FieldB]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
   |     |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_C]), SEEK:([MyDb].[dbo].[MyTable].[FieldC]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
   |--Clustered Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[PK_MyTable]), SEEK:([MyDb].[dbo].[MyTable].[ID]=[MyDb].[dbo].[MyTable].[ID]) LOOKUP ORDERED FORWARD)

Таким образом, похоже, используются оба индекса («Поиск индекса»).

Истекшее время для запроса: 00: 00: 00.2220127

Второй запрос, который я тестировал, использовал JOIN, чтобы избежать оператора ИЛИ (см. «Редактировать» в моем вопросе):

declare @p1 int = 1;
declare @p2 varchar(20) = '12345678';

select * from MyTable where FieldA=@p1 and FieldB=@p2
union
select * from MyTable where FieldA=@p1 and FieldC=@p2;

Этот запрос имеет следующий план запроса:

|--Merge Join(Union)
   |--Nested Loops(Inner Join, OUTER REFERENCES:([MyDb].[dbo].[MyTable].[ID]))
   |  |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_B]), SEEK:([MyDb].[dbo].[MyTable].[FieldB]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
   |  |--Clustered Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[PK_MyTable]), SEEK:([MyDb].[dbo].[MyTable].[ID]=[MyDb].[dbo].[MyTable].[ID]) LOOKUP ORDERED FORWARD)
   |--Nested Loops(Inner Join, OUTER REFERENCES:([MyDb].[dbo].[MyTable].[ID]))
      |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_C]), SEEK:([MyDb].[dbo].[MyTable].[FieldC]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
      |--Clustered Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[PK_MyTable]), SEEK:([MyDb].[dbo].[MyTable].[ID]=[MyDb].[dbo].[MyTable].[ID]) LOOKUP ORDERED FORWARD)

Снова используются оба индекса («Поиск по индексу»).

Истекшее время для запроса: 00: 00: 00.3710212

Примечание: Для обоих запросов не имеет значения, какая длина Iобъявите @ p2 с помощью: Использование varchar (8) или varchar (20) или varchar (30) дает те же результаты и планы запросов.

FollowИспользуя эти результаты, я остановлюсь на использовании оператора OR вместо UNION, поскольку оба запроса используют индексы, но первый выполняется быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...