Почему запрос с phone = N'1234 'медленнее, чем phone =' 1234 '?

У меня есть поле, которое является varchar (20)

Когда этот запрос выполняется, он выполняется быстро (использует поиск по индексу):

SELECT * FROM [dbo].[phone] WHERE phone = '5554474477'

Но этот медленный (использует сканирование индекса).

SELECT * FROM [dbo].[phone] WHERE phone = N'5554474477'

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

Ответ 1

Поскольку nvarchar имеет более высокий приоритет типов данных, чем varchar он должен выполнять неявное приведение столбца к nvarchar что предотвращает поиск по индексу.

При некоторых сопоставлениях он по-прежнему может использовать поиск и просто помещать cast в остаточный предикат к строкам, сопоставленным с поиском (вместо того, чтобы выполнять это для каждой строки во всей таблице посредством сканирования), но, вероятно, вы не ' т с использованием такого сопоставления.

Влияние сопоставления на это иллюстрируется ниже. При использовании сортировки SQL вы получаете сканирование, для сортировки Windows она вызывает внутреннюю функцию GetRangeThroughConvert и может преобразовать ее в GetRangeThroughConvert.

CREATE TABLE [dbo].[phone]
  (
     phone1 VARCHAR(500) COLLATE sql_latin1_general_cp1_ci_as CONSTRAINT uq1 UNIQUE,
     phone2 VARCHAR(500) COLLATE latin1_general_ci_as CONSTRAINT uq2 UNIQUE,
  );

SELECT phone1 FROM [dbo].[phone] WHERE phone1 = N'5554474477';
SELECT phone2 FROM [dbo].[phone] WHERE phone2 = N'5554474477';

enter image description here

SHOWPLAN_TEXT ниже

Запрос 1

  |--Index Scan(OBJECT:([tempdb].[dbo].[phone].[uq1]),  WHERE:(CONVERT_IMPLICIT(nvarchar(500),[tempdb].[dbo].[phone].[phone1],0)=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)))

Запрос 2

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1005], [Expr1006], [Expr1004]))
       |--Compute Scalar(DEFINE:(([Expr1005],[Expr1006],[Expr1004])=GetRangeThroughConvert([@1],[@1],(62))))
       |    |--Constant Scan
       |--Index Seek(OBJECT:([tempdb].[dbo].[phone].[uq2]), SEEK:([tempdb].[dbo].[phone].[phone2] > [Expr1005] AND [tempdb].[dbo].[phone].[phone2] < [Expr1006]),  WHERE:(CONVERT_IMPLICIT(nvarchar(500),[tempdb].[dbo].[phone].[phone2],0)=[@1]) ORDERED FORWARD)

Во втором случае вычислительный скаляр выдает следующие значения

Expr1004 = 62
Expr1005 = '5554474477'
Expr1006 = '5554474478'

Предикат поиска, показанный в плане, находится на phone2 > Expr1005 and phone2 < Expr1006 так что на phone2 > Expr1005 and phone2 < Expr1006 взгляд это исключило бы '5554474477' но флаг 62 означает, что это совпадает.

Ответ 2

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

Кастинг из NVARCHAR в VARCHAR - это сужающееся преобразование. То есть NVARCHAR имеет потенциально больше информации, чем аналогичное значение VARCHAR. Невозможно представить каждый вход NVARCHAR выходом VARCHAR, поэтому приведение от первого к последнему потенциально теряет некоторую информацию. Но противоположный состав - расширяющееся обращение. NVARCHAR значения VARCHAR значение NVARCHAR никогда не теряет информацию; это безопасно.

Теперь мы оба знаем, что ваше сужающееся преобразование также безопасно для этих конкретных данных. Но оптимизатор запросов Sql Server этого не знает. Он только видит информацию о типе данных при построении плана выполнения. Sql Server хочет кешировать свои планы выполнения и, возможно, повторно использовать их, чтобы сохранить работу по созданию тех же самых планов в будущем, и, следовательно, фактическое значение не так важно, как правильное общее преобразование.

Если для сравнения представлены два несовпадающих типа, Sql Server всегда выберет безопасное преобразование. Это же старая мантра "правильности козырей исполнения". Или, перефразируя Бенджамина Франклина: "Тот, кто обменял бы существенную правильность на небольшую производительность, не заслуживает ни правильности, ни производительности".

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

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

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

SELECT * FROM [dbo].[phone] WHERE phone = cast(N'5554474477' as varchar(20))

Конечно, это даже лучше исправить, используя в первую очередь значение VARCHAR:

SELECT * FROM [dbo].[phone] WHERE phone = '5554474477'

Если этот запрос был создан из программы .Net, он, вероятно, связан с AddWithValue(). Я писал об этой проблеме в прошлом и о том, как правильно ее решить.

Ответ 3

 SELECT * FROM [dbo].[phone] WHERE phone = N'5554474477'

интерпретируется как

 SELECT * from [dbo].[phone] WHERE CAST(phone as NVARCHAR) = N'5554474477'

что предотвращает использование индекса