Database
 sql >> база данни >  >> RDS >> Database

FORMAT() е хубаво и всичко, но...

Когато SQL Server 2012 все още беше в бета версия, писах в блог за новия FORMAT() функция:SQL Server v.Next (Denali) :CTP3 T-SQL подобрения:FORMAT().

По това време бях толкова развълнуван от новата функционалност, че дори не мислех да правя тестове за производителност. Разгледах това в по-скорошна публикация в блога, но единствено в контекста на премахване на времето от дата и час:Изрязване на време от дата и час – продължение.

Миналата седмица моят добър приятел Джейсън Хорнър (блог | @jasonhorner) ме троли с тези туитове:

Проблемът ми с това е точно този FORMAT() изглежда удобно, но е изключително неефективно в сравнение с други подходи (о, и това AS VARCHAR нещата също са зле). Ако правите това еднократно и за малки набори от резултати, няма да се притеснявам твърде много за това; но в мащаб може да стане доста скъпо. Нека илюстрирам с пример. Първо, нека създадем малка таблица с 1000 псевдослучайни дати:

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

Сега, нека заредим кеша с данните от тази таблица и илюстрираме три от често срещаните начина, по които хората са склонни да представят точно времето:

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

Сега нека изпълним отделни заявки, които използват тези различни техники. Ще ги стартираме всеки 5 пъти и ще изпълним следните варианти:

  1. Избиране на всички 1000 реда
  2. Избиране на TOP (1), подредено по групирания индексен ключ
  3. Присвояване на променлива (което принуждава пълно сканиране, но не позволява на изобразяването на SSMS да пречи на производителността)

Ето скрипта:

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

Сега можем да измерим производителността със следната заявка (моята система е доста тиха; на вашата може да се наложи да извършите по-разширено филтриране от просто 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;

Резултатите в моя случай бяха доста последователни:

Заявка (съкратена) Продължителност (микросекунди)
общо_изминало avg_elapsed total_clr
ИЗБЕРЕТЕ 1000 реда 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 изход (щракнете за увеличаване):

FORMAT() очевидно е губещият:avg_elapsed_time резултати (микросекунди)

Какво можем да научим от тези резултати (отново):

  1. Първо и най-важно, FORMAT() е скъпо .
  2. FORMAT() може, разбира се, да осигури повече гъвкавост и да даде по-интуитивни методи, които са съвместими с тези в други езици като C#. Въпреки това, в допълнение към неговите допълнителни разходи, и докато CONVERT() номерата на стилове са загадъчни и по-малко изчерпателни, така или иначе може да се наложи да използвате по-стария подход, тъй като FORMAT() е валиден само в SQL Server 2012 и по-нова версия.
  3. Дори в режим на готовност CONVERT() методът може да бъде драстично скъп (макар и изключително много в случая, когато SSMS трябваше да изобрази резултатите - той ясно обработва низовете по различен начин от стойностите на датата).
  4. Простото изтегляне на стойността за дата и час директно от базата данни винаги е било най-ефективно. Трябва да профилирате какво допълнително време е необходимо на вашето приложение да форматира датата по желание на нивото на презентацията - много е вероятно да не искате SQL Server изобщо да се занимава с красив формат (и всъщност мнозина биха спорили че тук винаги е мястото на тази логика).

Тук говорим само за микросекунди, но също така говорим само за 1000 реда. Увеличете това до действителните си размери на таблицата и въздействието от избора на грешен подход за форматиране може да бъде опустошително.

Ако искате да изпробвате този експеримент на собствената си машина, качих примерен скрипт:FormatIsNiceAndAllBut.sql_.zip


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Съвети за съхраняване на вашите резервни копия на TimescaleDB в облака

  2. Как да закръглите число до най-близкото цяло число в SQL

  3. Как да поръчам по две колони в SQL?

  4. Най-добрите подходи за групирана медиана

  5. Обявяване на общата наличност на SQL Safe Backup 8.7.2