Performance ha colpito usando CAST in T-SQL


12

Abbiamo un generatore SQL che emette istruzioni condizionali SQL genericamente per campi specifici (che per motivi di discussione: etichettiamo come myField).

Se myFieldè di tipo NVARCHAR, possiamo fare un confronto tra detto campo contro una stringa in questo modo: myField = 'foo'.

Tuttavia, questo non funziona per i campi di tipo NTEXT. Così, abbiamo a che fare il confronto con un cast: CAST(myField as NVARCHAR(MAX)) = 'foo'. Questo funzionerà infatti se myFieldè di tipo NVARCHARo NTEXT.

Qual è l'hit di performance di fare il cast di cui sopra su un campo che è già di tipo NVARCHAR? La mia speranza è che SQL Server sia abbastanza intelligente da riconoscere dinamicamente che myFieldè già di tipo NVARCHAR(trasformando CASTin modo efficace il no-op).


Una breve nota per chiunque trovi questa domanda: NTEXT (e TEXT e IMMAGINE) sono ufficialmente deprecati e dovrebbero essere rimossi in alcune versioni future di SQL Server (sebbene IIRC funzionino ancora in SQL1014), quindi dovresti usare NVARCHR (MAX) (o VARCHAR (MAX) o VARBINARY (MAX)) invece. Sostituire la colonna NTEXT con una NVARCHAR (MAX) in questa istanza eliminerebbe la necessità del cast poiché il confronto può essere fatto direttamente con quel tipo e ci sono altri potenziali guadagni di efficienza qui e altrove. Sfortunatamente non puoi indicizzare una colonna * (MAX), ma non puoi neanche una TEXT / NTEXT.
David Spillett,

Risposte:


12

Se il cast della colonna ha esattamente lo stesso tipo di dati e la stessa lunghezza e il predicato di ricerca è letterale, sembra davvero ignorarlo o trattarlo come un no-op e un indice cerca l'uguaglianza.

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

Se il cast della colonna ha lo stesso tipo di dati ma una lunghezza maggiore e il predicato di ricerca è letterale in una stringa, provoca una scansione dell'indice. Questo è ovviamente da evitare.

Se il cast della colonna ha lo stesso tipo di dati e la stessa o maggiore lunghezza e il predicato di ricerca è una variabile locale, aggiunge un operatore scalare di calcolo al piano di esecuzione. Questo chiama GetRangeThroughConverted emette un intervallo.

Questo intervallo viene utilizzato per eseguire una ricerca dell'indice e sembra piuttosto efficiente

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

Codice test

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

In generale, la CASTperformance verrà annullata perché invalida qualsiasi utilizzo dell'indice come mostrato dall'ultimo esempio di Martin Smith. Il cast su nvarchar(max)o su una lunghezza diversa significa un diverso tipo di dati: il fatto che sia tutto nvarcharè irrilevante.

Inoltre, anche il tipo di dati sul lato destro del confronto è importante. Se si tratta di una variabile locale o di un parametro di diversa lunghezza, un lato sarà implicitamente CASTal più ampio dei 2 tipi di dati (vedere la precedenza sul tipo di dati ).

Fondamentalmente, se hai un generale CASTad nvarchar(max)esso bollix le cose. Vorrei prendere in considerazione la correzione dell'uso di ntextprima di aggiungere CASTdappertutto.

La conversione potrebbe non essere visualizzata nel piano delle query. Vedi l'articolo del blog di Paul White


2

Solo una nota, lanciare in questo modo in cui Datecreated è datetime

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

Non interrompe la capacità di SQL di utilizzare gli indici in presenza di indici e, se non esistono, può comportare la registrazione di un indice mancante.

Allo stesso modo, quando si esegue il cast da intverso tinyinto bigintverso intecc., La funzione di cast non impedisce a SQL di utilizzare gli indici SE l'ottimizzatore sa che l'operazione di cast non modifica l'ordinamento dei 2 tipi di dati comparabili.

Ecco alcuni test che è possibile eseguire e visualizzare il piano effettivo utilizzando 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
cast come data può eseguire una ricerca indice ma presenta ancora problemi rispetto all'espressione come ricerca intervallo senza cast.
Martin Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.