Database
 sql >> Datenbank >  >> RDS >> Database

FORMAT() ist schön und gut, aber …

Als SQL Server 2012 noch in der Beta war, habe ich über das neue FORMAT() gebloggt Funktion:SQL Server v.Next (Denali) :CTP3 T-SQL-Verbesserungen :FORMAT().

Damals war ich so begeistert von der neuen Funktionalität, dass ich nicht einmal daran dachte, Performance-Tests durchzuführen. Ich habe dies in einem neueren Blog-Beitrag angesprochen, aber ausschließlich im Zusammenhang mit dem Stripping von Zeit und Datumszeit:Trimming time from datetime – a follow-up.

Letzte Woche hat mich mein guter Freund Jason Horner (Blog | @jasonhorner) mit diesen Tweets getrollt:

Mein Problem dabei ist nur das FORMAT() sieht bequem aus, ist aber im Vergleich zu anderen Ansätzen extrem ineffizient (oh, und das AS VARCHAR das Ding ist auch schlecht). Wenn Sie dieses Onesy-Twosy und für kleine Resultsets machen, würde ich mir darüber keine allzu großen Sorgen machen; aber im großen Maßstab kann es ziemlich teuer werden. Lassen Sie mich das an einem Beispiel veranschaulichen. Lassen Sie uns zuerst eine kleine Tabelle mit 1000 pseudozufälligen Daten erstellen:

SELECT TOP (1000) d = DATEADD(DAY, CHECKSUM(NEWID())%1000, o.create_date)
  INTO dbo.dtTest
  FROM sys.all_objects AS o
  ORDER BY NEWID();
GO
CREATE CLUSTERED INDEX d ON dbo.dtTest(d);

Lassen Sie uns nun den Cache mit den Daten aus dieser Tabelle füllen und drei der üblichen Arten veranschaulichen, wie Menschen dazu neigen, nur die Uhrzeit darzustellen:

SELECT d, 
  CONVERT(DATE, d), 
  CONVERT(CHAR(10), d, 120),
  FORMAT(d, 'yyyy-MM-dd')
FROM dbo.dtTest;

Lassen Sie uns nun einzelne Abfragen ausführen, die diese verschiedenen Techniken verwenden. Wir werden sie jeweils 5 Mal ausführen und wir werden die folgenden Variationen ausführen:

  1. Alle 1.000 Zeilen auswählen
  2. Auswahl von TOP (1), geordnet nach dem gruppierten Indexschlüssel
  3. Zuweisen zu einer Variablen (was einen vollständigen Scan erzwingt, aber verhindert, dass das SSMS-Rendering die Leistung beeinträchtigt)

Hier ist das Skript:

-- select all 1,000 rows
GO
SELECT d FROM dbo.dtTest;
GO 5
SELECT d = CONVERT(DATE, d) FROM dbo.dtTest;
GO 5
SELECT d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest;
GO 5
SELECT d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
GO 5
 
-- select top 1
GO
SELECT TOP (1) d FROM dbo.dtTest ORDER BY d;
GO 5
SELECT TOP (1) CONVERT(DATE, d) FROM dbo.dtTest ORDER BY d;
GO 5
SELECT TOP (1) CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ORDER BY d;
GO 5
SELECT TOP (1) FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest ORDER BY d;
GO 5
 
-- force scan but leave SSMS mostly out of it
GO
DECLARE @d DATE;
SELECT @d = d FROM dbo.dtTest;
GO 5
DECLARE @d DATE;
SELECT @d = CONVERT(DATE, d) FROM dbo.dtTest;
GO 5
DECLARE @d CHAR(10);
SELECT @d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest;
GO 5
DECLARE @d CHAR(10);
SELECT @d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
GO 5

Jetzt können wir die Leistung mit der folgenden Abfrage messen (mein System ist ziemlich leise; auf Ihrem müssen Sie möglicherweise eine erweiterte Filterung durchführen, als nur execution_count ):

SELECT 
  [t] = CONVERT(CHAR(255), t.[text]), 
  s.total_elapsed_time, 
  avg_elapsed_time = CONVERT(DECIMAL(12,2),s.total_elapsed_time / 5.0),
  s.total_worker_time, 
  avg_worker_time = CONVERT(DECIMAL(12,2),s.total_worker_time / 5.0),
  s.total_clr_time
FROM sys.dm_exec_query_stats AS s 
CROSS APPLY sys.dm_exec_sql_text(s.[sql_handle]) AS t
WHERE s.execution_count = 5
  AND t.[text] LIKE N'%dbo.dtTest%'
ORDER BY s.last_execution_time;

Die Ergebnisse in meinem Fall waren ziemlich konsistent:

Abfrage (abgeschnitten) Dauer (Mikrosekunden)
total_elapsed avg_elapsed total_clr
Wählen Sie 1.000 Zeilen aus SELECT d FROM dbo.dtTest ORDER BY d; 1,170 234.00 0
SELECT d = CONVERT(DATE, d) FROM dbo.dtTest ORDER BY d; 2,437 487.40 0
SELECT d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ORD ... 151,521 30,304.20 0
SELECT d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest ORDER ... 240,152 48,030.40 107,258
SELECT TOP (1) SELECT TOP (1) d FROM dbo.dtTest ORDER BY d; 251 50.20 0
SELECT TOP (1) CONVERT(DATE, d) FROM dbo.dtTest ORDER BY ... 440 88.00 0
SELECT TOP (1) CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ... 301 60.20 0
SELECT TOP (1) FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest O ... 1,094 218.80 589
Assign variable DECLARE @d DATE; SELECT @d = d FROM dbo.dtTest; 639 127.80 0
DECLARE @d DATE; SELECT @d = CONVERT(DATE, d) FROM dbo.d ... 644 128.80 0
DECLARE @d CHAR(10); SELECT @d = CONVERT(CHAR(10), d, 12 ... 1,972 394.40 0
DECLARE @d CHAR(10); SELECT @d = FORMAT(d, 'yyyy-MM-dd') ... 118,062 23,612.40 98,556

 

And to visualize the avg_elapsed_time zu visualisieren Ausgabe (zum Vergrößern anklicken):

FORMAT() ist eindeutig der Verlierer:avg_elapsed_time Ergebnisse (Mikrosekunden)

Was wir (erneut) aus diesen Ergebnissen lernen können:

  1. In erster Linie FORMAT() ist teuer .
  2. FORMAT() kann zugegebenermaßen mehr Flexibilität und intuitivere Methoden bieten, die mit denen in anderen Sprachen wie C# konsistent sind. Allerdings zusätzlich zu seinem Overhead und während CONVERT() Stilnummern sind kryptisch und weniger vollständig, Sie müssen möglicherweise trotzdem den älteren Ansatz verwenden, da FORMAT() ist nur in SQL Server 2012 und höher gültig.
  3. Sogar das Standby-CONVERT() -Methode kann drastisch teuer sein (allerdings nur in dem Fall, in dem SSMS die Ergebnisse rendern musste - es behandelt Zeichenfolgen eindeutig anders als Datumswerte).
  4. Es war immer am effizientesten, den datetime-Wert direkt aus der Datenbank zu ziehen. Sie sollten profilieren, wie viel zusätzliche Zeit Ihre Anwendung benötigt, um das Datum auf der Präsentationsebene wie gewünscht zu formatieren - es ist sehr wahrscheinlich, dass Sie SQL Server überhaupt nicht mit dem hübschen Format beschäftigen wollen (und tatsächlich würden viele argumentieren dass diese Logik immer dorthin gehört).

Wir sprechen hier nur von Mikrosekunden, aber wir sprechen auch nur von 1.000 Zeilen. Skalieren Sie das auf Ihre tatsächlichen Tabellengrößen, und die Auswirkungen der Wahl des falschen Formatierungsansatzes können verheerend sein.

Wenn Sie dieses Experiment auf Ihrem eigenen Computer ausprobieren möchten, habe ich ein Beispielskript hochgeladen:FormatIsNiceAndAllBut.sql_.zip