Kembali ketika SQL Server 2012 masih dalam versi beta, saya membuat blog tentang FORMAT()
new yang baru fungsi:SQL Server v.Next (Denali) :CTP3 T-SQL Enhancements :FORMAT().
Pada saat itu, saya sangat senang dengan fungsionalitas baru, sehingga saya bahkan tidak berpikir untuk melakukan pengujian kinerja apa pun. Saya memang membahas ini dalam posting blog yang lebih baru, tetapi semata-mata dalam konteks menghapus waktu dari datetime:Memotong waktu dari datetime – tindak lanjut.
Minggu lalu, teman baik saya Jason Horner (blog | @jasonhorner) menjebak saya dengan tweet ini:
| |
Masalah saya dengan ini hanyalah FORMAT()
terlihat nyaman, tetapi sangat tidak efisien dibandingkan dengan pendekatan lain (oh dan itu AS VARCHAR
hal yang buruk juga). Jika Anda melakukan onesy-twosy ini dan untuk hasil yang kecil, saya tidak akan terlalu mengkhawatirkannya; tetapi pada skala, itu bisa menjadi sangat mahal. Mari saya ilustrasikan dengan sebuah contoh. Pertama, mari kita buat tabel kecil dengan 1000 tanggal pseudo-acak:
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);
Sekarang, mari kita perbaiki cache dengan data dari tabel ini, dan ilustrasikan tiga cara umum yang cenderung dilakukan orang tepat waktu:
SELECT d, CONVERT(DATE, d), CONVERT(CHAR(10), d, 120), FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
Sekarang, mari kita lakukan kueri individual yang menggunakan teknik berbeda ini. Kami akan menjalankannya setiap 5 kali dan kami akan menjalankan variasi berikut:
- Memilih semua 1.000 baris
- Memilih TOP (1) yang diurutkan berdasarkan kunci indeks berkerumun
- Menetapkan ke variabel (yang memaksa pemindaian penuh, tetapi mencegah rendering SSMS mengganggu kinerja)
Berikut scriptnya:
-- 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
Sekarang, kami dapat mengukur kinerja dengan kueri berikut (sistem saya cukup tenang; pada sistem Anda, Anda mungkin perlu melakukan pemfilteran lebih lanjut daripada hanya 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;
Hasil dalam kasus saya cukup konsisten:
Kueri (terpotong) | Durasi (mikrodetik) | |||
---|---|---|---|---|
total_berlalu | avg_elapsed | total_clr | ||
PILIH 1.000 baris | 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
keluaran (klik untuk memperbesar):
FORMAT() jelas kalah :hasil avg_elapsed_time (mikrodetik)
Apa yang dapat kita pelajari dari hasil ini (sekali lagi):
- Pertama dan terpenting,
FORMAT()
mahal . FORMAT()
dapat, memang, memberikan lebih banyak fleksibilitas dan memberikan metode yang lebih intuitif yang konsisten dengan yang ada dalam bahasa lain seperti C#. Namun, selain overhead, dan sementaraCONVERT()
nomor gaya samar dan kurang lengkap, Anda mungkin harus menggunakan pendekatan yang lebih lama, karenaFORMAT()
hanya berlaku di SQL Server 2012 dan yang lebih baru.- Bahkan standby
CONVERT()
metode bisa sangat mahal (meskipun hanya sangat parah dalam kasus di mana SSMS harus membuat hasil - itu jelas menangani string berbeda dari nilai tanggal). - Hanya menarik nilai datetime langsung dari database selalu yang paling efisien. Anda harus membuat profil waktu tambahan yang diperlukan aplikasi Anda untuk memformat tanggal seperti yang diinginkan pada tingkat presentasi - kemungkinan besar Anda tidak ingin SQL Server terlibat dengan format cantik sama sekali (dan pada kenyataannya banyak yang akan berdebat bahwa di sinilah logika itu selalu berada).
Kami hanya berbicara mikrodetik di sini, tetapi kami juga hanya berbicara 1.000 baris. Skalakan itu ke ukuran tabel Anda yang sebenarnya, dan dampak dari memilih pendekatan pemformatan yang salah bisa sangat merusak.
Jika Anda ingin mencoba eksperimen ini di komputer Anda sendiri, saya telah mengunggah contoh skrip:FormatIsNiceAndAllBut.sql_.zip