Показники продуктивності за допомогою 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на не-оп).


Швидка примітка для тих, хто знайде це питання: 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)це bollix речі. Я б розглядав можливість виправити використання, ntextперш ніж додавати CASTвсе.

Перетворення може не відображатися в плані запитів. Дивіться статтю в блозі Пола Уайта


2

Лише зауваження. Лиття на зразок цього, коли Datecreate - дата

 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
Лист як дата може робити пошук в індексах, але все ж виникають проблеми порівняно з вираженням як пошук у діапазоні без вибору.
Мартін Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.