Leistungseinbußen bei Verwendung von CAST in T-SQL

12

Wir haben einen SQL-Generator, der generische SQL-Bedingungsanweisungen für bestimmte Felder ausgibt (was zur Diskussion: Wir werden als kennzeichnen myField).

Wenn myFieldes sich um einen Typ handelt NVARCHAR, können wir dieses Feld mit einer Zeichenfolge wie folgt vergleichen : myField = 'foo'.

Dies funktioniert jedoch nicht für Felder vom Typ NTEXT. Wir müssen also den Vergleich mit einer Besetzung anstellen : CAST(myField as NVARCHAR(MAX)) = 'foo'. Dies funktioniert in der Tat, wenn myFieldes sich um einen Typ handelt NVARCHARoder NTEXT.

Was ist der Leistungshit, wenn die oben genannte Besetzung auf einem Feld ausgeführt wird, das bereits vom Typ ist NVARCHAR? Ich hoffe, dass SQL Server intelligent genug ist, um dynamisch zu erkennen, dass myFieldes sich bereits um einen Typ handelt NVARCHAR(der effektiv CASTzu einem No-Op wird).

Paul White 9
quelle
Ein kurzer Hinweis für alle, die diese Frage finden: NTEXT (und TEXT und IMAGE) sind offiziell veraltet und müssen in einer zukünftigen Version von SQL Server entfernt werden (obwohl IIRC in SQL1014 noch funktioniert). Sie sollten daher NVARCHR (MAX) verwenden. (oder VARCHAR (MAX) oder VARBINARY (MAX)) stattdessen. Das Ersetzen der NTEXT-Spalte durch eine NVARCHAR (MAX) -Spalte würde in diesem Fall die Notwendigkeit der Umwandlung beseitigen, da der Vergleich direkt mit diesem Typ durchgeführt werden kann und es auch hier und anderswo andere potenzielle Effizienzgewinne gibt. Leider können Sie keine * (MAX) -Spalte indizieren, aber Sie können auch keine TEXT / NTEXT-Spalte.
David Spillett

Antworten:

12

Wenn die Besetzung der Spalte genau den gleichen Datentyp und die gleiche Länge hat und das Suchprädikat ein Literal ist, scheint es dies tatsächlich zu ignorieren oder als No-Op zu behandeln, und es sucht ein Index nach Gleichheit.

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

Wenn die Umwandlung der Spalte denselben Datentyp hat, aber länger ist und das Suchprädikat ein Zeichenfolgenliteral ist, wird ein Index-Scan durchgeführt. Dies ist natürlich zu vermeiden.

Wenn die Umwandlung der Spalte denselben Datentyp und dieselbe oder eine größere Länge hat und das Suchprädikat eine lokale Variable ist, wird dem Ausführungsplan ein Berechnungsskalaroperator hinzugefügt. Dies ruft GetRangeThroughConverteinen Bereich auf und gibt ihn aus.

Dieser Bereich wird für eine Indexsuche verwendet und scheint ziemlich effizient zu sein

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

Code testen

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
Martin Smith
quelle
6

Im Allgemeinen CASTwird die Leistung beeinträchtigt, da die Verwendung von Indexsuchen ungültig wird, wie das letzte Beispiel von Martin Smith zeigt. Das Casting auf nvarchar(max)oder auf eine andere Länge bedeutet einen anderen Datentyp: Die Tatsache, dass alles nvarcharist, ist irrelevant.

Darüber hinaus spielt auch der Datentyp auf der rechten Seite des Vergleichs eine Rolle. Wenn es sich um eine lokale Variable oder einen Parameter mit einer anderen Länge handelt, ist eine Seite implizit CASTder breiteste der beiden Datentypen (siehe Priorität des Datentyps ).

Grundsätzlich, wenn Sie einen General CASTdazu nvarchar(max)haben, wird die Dinge durcheinander bringen. Ich würde in Betracht ziehen, die Verwendung von zu korrigieren, ntextbevor ich CASTalles hinzufügte .

Die Konvertierung wird möglicherweise nicht im Abfrageplan angezeigt. Siehe Paul Whites Blog-Artikel

gbn
quelle
2

Nur eine Anmerkung: Casting wie dieses, bei dem Datecreated datetime ist

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

Beeinträchtigt nicht die Fähigkeit von SQL, Indizes zu verwenden, wenn Indizes vorhanden sind, und wenn sie nicht vorhanden sind, kann dies dazu führen, dass ein fehlender Index protokolliert wird.

In ähnlicher Weise hindert die Cast-Funktion SQL beim Casting von intnach tinyintoder bigintnach intusw. nicht daran, Indizes zu verwenden , wenn der Optimierer weiß, dass die Cast-Operation die Sortierreihenfolge der beiden vergleichbaren Datentypen nicht ändert.

Hier finden Sie eine Reihe von Tests, die Sie mit Adventureworks2008R2 ausführen und den aktuellen Plan anzeigen können

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
Doran Mackay
quelle
1
Besetzung als Datum kann eine Indexsuche durchführen, hat jedoch immer noch Probleme im Vergleich zum Ausdruck als Bereichssuche ohne Besetzung.
Martin Smith