Падение производительности при использовании CAST в T-SQL

12

У нас есть генератор SQL, который генерирует условные операторы SQL в общем случае для указанных полей (которые для обсуждения: мы будем обозначать как myField).

Если myFieldимеет тип NVARCHAR, мы можем сделать сравнение указанного поля со строкой , как так: myField = 'foo'.

Однако это не работает для полей типа NTEXT. Таким образом, мы должны сделать сравнение с броском: CAST(myField as NVARCHAR(MAX)) = 'foo'. Это на самом деле будет работать, если myFieldимеет тип NVARCHARили NTEXT.

Какова производительность при выполнении вышеупомянутого приведения на поле, которое уже имеет тип NVARCHAR? Я надеюсь, что SQL Server достаточно умен, чтобы динамически распознавать, что myFieldуже имеет тип NVARCHAR(эффективно превращая его CASTв неоперативный).

Пол Уайт 9
источник
Небольшое примечание для любого, кто найдет этот вопрос: NTEXT (и TEXT и IMAGE) официально устарели и должны быть удалены в какой-либо будущей версии SQL Server (хотя IIRC все еще работает в SQL1014), поэтому вам следует использовать NVARCHR (MAX) (или VARCHAR (MAX) или VARBINARY (MAX)) вместо этого. Замена столбца NTEXT на столбец NVARCHAR (MAX) в этом случае устранит необходимость в приведении, так как сравнение может быть выполнено непосредственно с этим типом, а также есть другие потенциальные улучшения эффективности здесь и в других местах. К сожалению, вы не можете индексировать столбец * (MAX), но вы также не можете индексировать столбец TEXT / NTEXT.
Дэвид Спиллетт

Ответы:

12

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

Seek Keys[1]: Prefix: [tempdb].[dbo].[#test].name = Scalar Operator(N'rpc')

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

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

Этот диапазон используется для поиска по индексу и кажется довольно эффективным

Seek Keys[1]: 
Start: [tempdb].[dbo].[#test].name > Scalar Operator([Expr1006]), 
End: [tempdb].[dbo].[#test].name < Scalar Operator([Expr1007])

Код тестирования

SELECT *
 INTO #test
  FROM [master].[dbo].[spt_values]

CREATE NONCLUSTERED INDEX [ixname] ON #test
(
    [name] ASC
)

DECLARE @name NVARCHAR(MAX)

SET @name = 'rpc'

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(35))= @name --Cast the same and local variable

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(MAX))=@name --Cast to longer and local variable

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(35))='rpc' --Cast the same and literal

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(MAX))='rpc' --Cast to longer and literal
Мартин Смит
источник
6

В целом, производительность CASTубьет, потому что она делает недействительным любое использование поиска индекса, как показывает последний пример Мартина Смита. Приведение к nvarchar(max)или к другой длине означает другой тип данных: факт, что все nvarcharэто не имеет значения.

Кроме того, тип данных правой части сравнения также имеет значение. Если это локальная переменная или параметр другой длины, то одна сторона будет неявно указана CASTдля самого широкого из двух типов данных (см. Приоритет типа данных ).

По сути, если у вас есть общие CASTдля nvarchar(max)него, это запутает вещи. Я хотел бы рассмотреть вопрос об использовании, ntextпрежде чем я добавил CASTвсе снова.

Конверсия может не отображаться в плане запроса. Смотрите статью в блоге Пола Уайта

ГБН
источник
2

Просто примечание, Кастинг как этот, где Datecreated является datetime

 Cast (Datecreated as date) = cast(@MydatetimeValue as date)

Не нарушает способность SQL использовать индексы, если индексы существуют, и если они не существуют, может привести к регистрации отсутствующего индекса.

Аналогично, при приведении intк tinyintили bigintи intт. Д. Функция приведения не останавливает использование SQL индексами, ЕСЛИ оптимизатор знает, что операция приведения не меняет порядок сортировки 2 сопоставимых типов данных.

Вот несколько тестов, которые вы можете запустить и просмотреть фактический план, используя Adventureworks2008R2.

select count(*) from Sales.SalesOrderDetail where SalesOrderID = 8 --1
select top 10 * from Sales.SalesOrderDetail where cast(SalesOrderID as tinyint) = 8  --2
select top 10 * from Sales.SalesOrderDetail where cast(SalesOrderID as bigint) = 8  --3
select top 10 SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate  as date) = '19780322' --4
select top 10 SalesOrderID from Sales.SalesOrderDetail where convert(date,ModifiedDate) = '19780322'  --5
select top 10 SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate as varchar(20)) = '1978'  --6 -- THIS WILL NOT USE INDEX
select  SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate  as date) between '19780101' and '19780109'  --7
Доран Маккей
источник
1
приведение к дате может выполнить поиск по индексу, но по- прежнему возникают проблемы по сравнению с выражением поиска по диапазону без приведения.
Мартин Смит