sql >> Databáze >  >> RDS >> Database

FORMAT() je hezké, ale…

Když byl SQL Server 2012 stále ve verzi beta, napsal jsem blog o novém FORMAT() funkce:SQL Server v.Next (Denali) :CTP3 Vylepšení T-SQL :FORMAT().

V té době jsem byl z nové funkcionality tak nadšený, že mě ani nenapadlo provádět nějaké testování výkonu. Řešil jsem to v novějším příspěvku na blogu, ale výhradně v souvislosti s odstraňováním času z data a času:Oříznutí času z data a času – následná činnost.

Minulý týden mě můj dobrý přítel Jason Horner (blog | @jasonhorner) tweetoval těmito tweety:

Můj problém je právě v tom FORMAT() vypadá pohodlně, ale ve srovnání s jinými přístupy je extrémně neefektivní (aha a to AS VARCHAR věc je taky špatná). Pokud to děláte jednou za dva a pro malé sady výsledků, moc bych se tím netrápil; ale v měřítku to může být pěkně drahé. Dovolte mi to ilustrovat na příkladu. Nejprve vytvořte malou tabulku s 1000 pseudonáhodnými daty:

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

Nyní naplníme mezipaměť daty z této tabulky a ilustrujme tři běžné způsoby, jakými lidé mají tendenci prezentovat právě čas:

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

Nyní proveďte jednotlivé dotazy, které používají tyto různé techniky. Spustíme je každý 5krát a spustíme následující varianty:

  1. Výběr všech 1 000 řádků
  2. Výběr TOP (1) seřazený podle seskupeného indexového klíče
  3. Přiřazení k proměnné (což vynutí úplné skenování, ale zabrání tomu, aby vykreslování SSMS narušovalo výkon)

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

Nyní můžeme měřit výkon pomocí následujícího dotazu (můj systém je docela tichý; na vašem možná budete muset provést pokročilejší filtrování než jen 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;

Výsledky v mém případě byly poměrně konzistentní:

Dotaz (zkrácený) Trvání (mikrosekundy)
total_elapsed avg_elapsed total_clr
VYBERTE 1 000 řádků 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 výstup (kliknutím zvětšíte):

FORMAT() jednoznačně prohrává:výsledky avg_elapsed_time (mikrosekundy)

Co se můžeme z těchto výsledků (opět) naučit:

  1. V první řadě FORMAT() je drahý .
  2. FORMAT() může samozřejmě poskytnout větší flexibilitu a poskytnout intuitivnější metody, které jsou konzistentní s těmi v jiných jazycích, jako je C#. Nicméně kromě jeho režie a zároveň CONVERT() čísla stylů jsou záhadná a méně vyčerpávající, možná budete muset stejně použít starší přístup, protože FORMAT() je platný pouze pro SQL Server 2012 a novější.
  3. Dokonce i v pohotovostním režimu CONVERT() metoda může být drasticky drahá (i když jen silně v případě, kdy SSMS musel vykreslit výsledky - jasně zpracovává řetězce jinak než hodnoty data).
  4. Pouze vytažení hodnoty datetime přímo z databáze bylo vždy nejefektivnější. Měli byste si vyprofilovat, kolik času navíc zabere vaší aplikaci na formátování data podle potřeby v prezentační vrstvě – je vysoce pravděpodobné, že nebudete chtít, aby se SQL Server vůbec zapojoval do zkrášlujícího formátu (a ve skutečnosti by mnozí argumentovali že sem ta logika vždy patří).

Mluvíme zde pouze o mikrosekundách, ale také mluvíme pouze o 1000 řádcích. Přizpůsobte to skutečným velikostem tabulky a dopad nesprávného přístupu k formátování může být zničující.

Pokud si chcete tento experiment vyzkoušet na svém vlastním počítači, nahrál jsem vzorový skript:FormatIsNiceAndAllBut.sql_.zip


  1. Jak STRCMP() funguje v MariaDB

  2. Serializovatelná úroveň izolace

  3. Volání funkce pl/sql v Javě?

  4. Jaká je maximální velikost VARCHAR2 v PL/SQL a SQL?