sql >> Database teknologi >  >> RDS >> Database

FORMAT() er godt og det hele, men...

Dengang SQL Server 2012 stadig var i beta, bloggede jeg om den nye FORMAT() funktion:SQL Server v.Next (Denali):CTP3 T-SQL-forbedringer:FORMAT().

På det tidspunkt var jeg så begejstret for den nye funktionalitet, at jeg ikke engang tænkte på at lave nogen præstationstest. Jeg adresserede dette i et nyere blogindlæg, men udelukkende i forbindelse med at fjerne tid fra et datetime:Trimning af tid fra datetime – en opfølgning.

I sidste uge troldede min gode ven Jason Horner (blog | @jasonhorner) mig med disse tweets:

Mit problem med dette er bare at FORMAT() ser praktisk ud, men det er ekstremt ineffektivt sammenlignet med andre tilgange (oh og det AS VARCHAR ting er også dårligt). Hvis du laver dette enkeltvis og for små resultater, ville jeg ikke bekymre mig for meget om det; men i skala kan det blive ret dyrt. Lad mig illustrere med et eksempel. Lad os først oprette en lille tabel med 1000 pseudo-tilfældige datoer:

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);

Lad os nu prime cachen med data fra denne tabel og illustrere tre af de almindelige måder, folk har tendens til at præsentere netop tiden på:

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

Lad os nu udføre individuelle forespørgsler, der bruger disse forskellige teknikker. Vi kører dem hver 5 gange, og vi kører følgende varianter:

  1. Valg af alle 1.000 rækker
  2. Valger TOP (1) sorteret efter den klyngede indeksnøgle
  3. Tildeling til en variabel (som fremtvinger en fuld scanning, men forhindrer SSMS-gengivelse i at forstyrre ydeevnen)

Her er scriptet:

-- 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

Nu kan vi måle ydeevnen med følgende forespørgsel (mit system er ret stille; på dit skal du muligvis udføre mere avanceret filtrering end blot 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;

Resultaterne i mit tilfælde var ret konsistente:

Forespørgsel (trunkeret) Varighed (mikrosekunder)
total_elapsed avg_elapsed total_clr
VÆLG 1.000 rækker 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 output (klik for at forstørre):

FORMAT() er klart taberen:resultater for avg_elapsed_time (mikrosekunder)

Hvad vi kan lære af disse resultater (igen):

  1. Først og fremmest FORMAT() er dyrt .
  2. FORMAT() kan ganske vist give mere fleksibilitet og give mere intuitive metoder, der stemmer overens med dem på andre sprog som C#. Men ud over dets overhead, og mens CONVERT() stiltal er kryptiske og mindre udtømmende, du skal muligvis bruge den ældre tilgang alligevel, da FORMAT() er kun gyldig i SQL Server 2012 og nyere.
  3. Selv standby CONVERT() metode kan være drastisk dyr (dog kun alvorligt i det tilfælde, hvor SSMS skulle gengive resultaterne - den håndterer tydeligt strenge anderledes end datoværdier).
  4. Bare at trække datetime-værdien direkte ud af databasen var altid mest effektivt. Du bør profilere, hvilken ekstra tid det tager for din ansøgning at formatere datoen som ønsket på præsentationsniveauet - det er højst sandsynligt, at du slet ikke vil have SQL Server til at involvere sig i prettying-formatet (og faktisk vil mange hævde at det er her den logik altid hører hjemme).

Vi taler kun om mikrosekunder her, men vi taler også kun om 1.000 rækker. Skaler det ud til dine faktiske tabelstørrelser, og virkningen af ​​at vælge den forkerte formateringstilgang kan være ødelæggende.

Hvis du vil prøve dette eksperiment på din egen maskine, har jeg uploadet et eksempelscript:FormatIsNiceAndAllBut.sql_.zip


  1. MySQL vælg én kolonne DISTINCT, med tilsvarende andre kolonner

  2. Sådan formateres SQLite-forespørgselsresultater i TCL-tilstand

  3. cx_Oracle og undtagelseshåndtering - god praksis?

  4. MySQL High Availability Framework Forklaret – Del I:Introduktion