Catatan:Posting ini awalnya diterbitkan hanya di eBook kami, High Performance Techniques for SQL Server, Volume 2. Anda dapat mengetahui tentang eBook kami di sini. Perhatikan juga bahwa beberapa hal ini dapat berubah dengan peningkatan yang direncanakan untuk OLTP Dalam Memori di SQL Server 2016.
Ada beberapa kebiasaan dan praktik terbaik yang banyak dari kita kembangkan dari waktu ke waktu terkait dengan kode Transact-SQL. Dengan prosedur tersimpan khususnya, kami berusaha untuk melewatkan nilai parameter dari tipe data yang benar, dan memberi nama parameter kami secara eksplisit daripada hanya mengandalkan posisi ordinal. Namun, kadang-kadang, kita mungkin malas tentang ini:kita mungkin lupa memberi awalan string Unicode dengan N
, atau cukup daftarkan konstanta atau variabel secara berurutan alih-alih menentukan nama parameter. Atau keduanya.
Di SQL Server 2014, jika Anda menggunakan In-Memory OLTP ("Hekaton") dan prosedur yang dikompilasi secara asli, Anda mungkin ingin sedikit menyesuaikan pemikiran Anda tentang hal-hal ini. Saya akan mendemonstrasikan dengan beberapa kode terhadap Sampel OLTP Dalam Memori SQL Server 2014 RTM di CodePlex, yang memperluas database sampel AdventureWorks2012. (Jika Anda akan mengatur ini dari awal untuk mengikuti, silakan lihat sekilas pengamatan saya di posting sebelumnya.)
Mari kita lihat tanda tangan untuk prosedur tersimpan Sales.usp_InsertSpecialOffer_inmem
:
CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer_inmem] @Description NVARCHAR(255) NOT NULL, @DiscountPct SMALLMONEY NOT NULL = 0, @Type NVARCHAR(50) NOT NULL, @Category NVARCHAR(50) NOT NULL, @StartDate DATETIME2 NOT NULL, @EndDate DATETIME2 NOT NULL, @MinQty INT NOT NULL = 0, @MaxQty INT = NULL, @SpecialOfferID INT OUTPUT WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL=SNAPSHOT, LANGUAGE=N'us_english') DECLARE @msg nvarchar(256) -- validation removed for brevity INSERT Sales.SpecialOffer_inmem (Description, DiscountPct, Type, Category, StartDate, EndDate, MinQty, MaxQty) VALUES (@Description, @DiscountPct, @Type, @Category, @StartDate, @EndDate, @MinQty, @MaxQty) SET @SpecialOfferID = SCOPE_IDENTITY() END GO
Saya ingin tahu apakah penting apakah parameter diberi nama, atau jika prosedur yang dikompilasi secara asli menangani konversi implisit sebagai argumen untuk prosedur tersimpan lebih baik daripada prosedur tersimpan tradisional. Pertama saya membuat salinan Sales.usp_InsertSpecialOffer_inmem
sebagai prosedur tersimpan tradisional – ini hanya melibatkan penghapusan ATOMIC
memblokir dan menghapus NOT NULL
deklarasi dari parameter input:
CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer] @Description NVARCHAR(255), @DiscountPct SMALLMONEY = 0, @Type NVARCHAR(50), @Category NVARCHAR(50), @StartDate DATETIME2, @EndDate DATETIME2, @MinQty INT = 0, @MaxQty INT = NULL, @SpecialOfferID INT OUTPUT AS BEGIN DECLARE @msg nvarchar(256) -- validation removed for brevity INSERT Sales.SpecialOffer_inmem (Description, DiscountPct, Type, Category, StartDate, EndDate, MinQty, MaxQty) VALUES (@Description, @DiscountPct, @Type, @Category, @StartDate, @EndDate, @MinQty, @MaxQty) SET @SpecialOfferID = SCOPE_IDENTITY() END GO
Untuk meminimalkan pergeseran kriteria, prosedur masih menyisipkan ke dalam tabel versi In-Memory, Sales.SpecialOffer_inmem.
Kemudian saya ingin mengatur waktu 100.000 panggilan ke kedua salinan prosedur tersimpan dengan kriteria ini:
Menggunakan kumpulan berikut, disalin untuk versi tradisional prosedur tersimpan (cukup hapus _inmem
dari empat EXEC
panggilan):
SET NOCOUNT ON; CREATE TABLE #x ( i INT IDENTITY(1,1), d VARCHAR(32), s DATETIME2(7) NOT NULL DEFAULT SYSDATETIME(), e DATETIME2(7) ); GO INSERT #x(d) VALUES('Named, proper types'); GO /* this uses named parameters, and uses correct data types */ DECLARE @p1 NVARCHAR(255) = N'Product 1', @p2 SMALLMONEY = 10, @p3 NVARCHAR(50) = N'Volume Discount', @p4 NVARCHAR(50) = N'Reseller', @p5 DATETIME2 = '20140615', @p6 DATETIME2 = '20140620', @p7 INT = 10, @p8 INT = 20, @p9 INT; EXEC Sales.usp_InsertSpecialOffer_inmem @Description = @p1, @DiscountPct = @p2, @Type = @p3, @Category = @p4, @StartDate = @p5, @EndDate = @p6, @MinQty = @p7, @MaxQty = @p8, @SpecialOfferID = @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 1; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO INSERT #x(d) VALUES('Not named, proper types'); GO /* this does not use named parameters, but uses correct data types */ DECLARE @p1 NVARCHAR(255) = N'Product 1', @p2 SMALLMONEY = 10, @p3 NVARCHAR(50) = N'Volume Discount', @p4 NVARCHAR(50) = N'Reseller', @p5 DATETIME2 = '20140615', @p6 DATETIME2 = '20140620', @p7 INT = 10, @p8 INT = 20, @p9 INT; EXEC Sales.usp_InsertSpecialOffer_inmem @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 2; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO INSERT #x(d) VALUES('Named, improper types'); GO /* this uses named parameters, but incorrect data types */ DECLARE @p1 VARCHAR(255) = 'Product 1', @p2 DECIMAL(10,2) = 10, @p3 VARCHAR(255) = 'Volume Discount', @p4 VARCHAR(32) = 'Reseller', @p5 DATETIME = '20140615', @p6 CHAR(8) = '20140620', @p7 TINYINT = 10, @p8 DECIMAL(10,2) = 20, @p9 BIGINT; EXEC Sales.usp_InsertSpecialOffer_inmem @Description = @p1, @DiscountPct = @p2, @Type = @p3, @Category = @p4, @StartDate = @p5, @EndDate = @p6, @MinQty = '10', @MaxQty = @p8, @SpecialOfferID = @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 3; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO INSERT #x(d) VALUES('Not named, improper types'); GO /* this does not use named parameters, and uses incorrect data types */ DECLARE @p1 VARCHAR(255) = 'Product 1', @p2 DECIMAL(10,2) = 10, @p3 VARCHAR(255) = 'Volume Discount', @p4 VARCHAR(32) = 'Reseller', @p5 DATETIME = '20140615', @p6 CHAR(8) = '20140620', @p7 TINYINT = 10, @p8 DECIMAL(10,2) = 20, @p9 BIGINT; EXEC Sales.usp_InsertSpecialOffer_inmem @p1, @p2, @p3, @p4, @p5, @p6, '10', @p8, @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 4; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO SELECT d, duration_ms = DATEDIFF(MILLISECOND, s, e) FROM #x; GO DROP TABLE #x; GO
Saya menjalankan setiap tes 10 kali, dan berikut adalah durasi rata-rata, dalam milidetik:
Prosedur Tersimpan Tradisional | |
---|---|
Parameter | Durasi Rata-rata (milidetik) |
Bernama, jenis yang tepat | 72.132 |
Tidak disebutkan namanya, jenis yang tepat | 72.846 |
Bernama, jenis yang tidak tepat | 76,154 |
Tidak disebutkan namanya, jenis yang tidak tepat | 76.902 |
Prosedur Terkompilasi Secara Asli | |
Parameter | Durasi Rata-rata (milidetik) |
Bernama, jenis yang tepat | 63,202 |
Tidak disebutkan namanya, jenis yang tepat | 61,297 |
Bernama, jenis yang tidak tepat | 64.560 |
Tidak disebutkan namanya, jenis yang tidak tepat | 64,288 |
Durasi rata-rata, dalam milidetik, dari berbagai metode panggilan
Dengan prosedur tersimpan tradisional, jelas bahwa menggunakan tipe data yang salah memiliki dampak besar pada kinerja (perbedaan sekitar 4 detik), sementara tidak menyebutkan parameter memiliki efek yang jauh lebih dramatis (menambahkan sekitar 700 ms). Saya selalu mencoba mengikuti praktik terbaik dan menggunakan tipe data yang tepat serta memberi nama semua parameter, dan pengujian kecil ini tampaknya mengonfirmasi bahwa hal itu dapat bermanfaat.
Dengan prosedur tersimpan yang dikompilasi secara asli, menggunakan tipe data yang salah masih menyebabkan penurunan kinerja yang sama seperti prosedur tersimpan tradisional. Namun kali ini, memberi nama parameter tidak banyak membantu; sebenarnya, itu memiliki dampak negatif, menambahkan hampir dua detik ke durasi keseluruhan. Agar adil, ini adalah jumlah panggilan yang besar dalam waktu yang cukup singkat, tetapi jika Anda mencoba memeras kinerja paling mutakhir yang dapat Anda lakukan dari fitur ini, setiap nanodetik sangat berarti.
Menemukan Masalah
Bagaimana Anda bisa tahu jika prosedur tersimpan yang dikompilasi secara asli dipanggil dengan salah satu dari metode "lambat" ini? Ada XEvent untuk itu! Acara ini disebut natively_compiled_proc_slow_parameter_passing
, dan tampaknya tidak didokumentasikan di Buku Daring saat ini. Anda dapat membuat sesi Acara yang Diperpanjang berikut untuk memantau acara ini:
CREATE EVENT SESSION [XTP_Parameter_Events] ON SERVER ADD EVENT sqlserver.natively_compiled_proc_slow_parameter_passing ( ACTION(sqlserver.sql_text) ) ADD TARGET package0.event_file(SET filename=N'C:\temp\XTPParams.xel'); GO ALTER EVENT SESSION [XTP_Parameter_Events] ON SERVER STATE = START;
Setelah sesi berjalan, Anda dapat mencoba salah satu dari empat panggilan di atas satu per satu, lalu Anda dapat menjalankan kueri ini:
;WITH x([timestamp], db, [object_id], reason, batch) AS ( SELECT xe.d.value(N'(event/@timestamp)[1]',N'datetime2(0)'), DB_NAME(xe.d.value(N'(event/data[@name="database_id"]/value)[1]',N'int')), xe.d.value(N'(event/data[@name="object_id"]/value)[1]',N'int'), xe.d.value(N'(event/data[@name="reason"]/text)[1]',N'sysname'), xe.d.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)') FROM sys.fn_xe_file_target_read_file(N'C:\temp\XTPParams*.xel',NULL,NULL,NULL) AS ft CROSS APPLY (SELECT CONVERT(XML, ft.event_data)) AS xe(d) ) SELECT [timestamp], db, [object_id], reason, batch FROM x;
Bergantung pada apa yang Anda jalankan, Anda akan melihat hasil yang mirip dengan ini:
stempel waktu | db | object_id | alasan | batch |
---|---|---|---|---|
01-07-2014 16:23:14 | AdventureWorks2012 | 2087678485 | named_parameters | DECLARE @p1 NVARCHAR(255) = N'Product 1', @p2 SMALLMONEY = 10, @p3 NVARCHAR(50) = N'Volume Discount', @p4 NVARCHAR(50) = N'Reseller', @p5 DATETIME2 = '20140615', @p6 DATETIME2 = '20140620', @p7 INT = 10, @p8 INT = 20, @p9 INT; EXEC Sales.usp_InsertSpecialOffer_inmem @Description = @p1, @DiscountPct = @p2, @Type = @p3, @Category = @p4, @StartDate = @p5, @EndDate = @p6, @MinQty = @p7, @MaxQty = @p8, @SpecialOfferID = @p9 OUTPUT; |
01-07-2014 16:23:22 | AdventureWorks2012 | 2087678485 | konversi_parameter | DECLARE @p1 VARCHAR(255) = 'Product 1', @p2 DECIMAL(10,2) = 10, @p3 VARCHAR(255) = 'Volume Discount', @p4 VARCHAR(32) = 'Reseller', @p5 DATETIME = '20140615', @p6 CHAR(8) = '20140620', @p7 TINYINT = 10, @p8 DECIMAL(10,2) = 20, @p9 BIGINT; EXEC Sales.usp_InsertSpecialOffer_inmem @p1, @p2, @p3, @p4, @p5, @p6, '10', @p8, @p9 OUTPUT; |
Contoh hasil dari Acara yang Diperpanjang
Semoga batch
kolom sudah cukup untuk mengidentifikasi pelakunya, tetapi jika Anda memiliki kumpulan besar yang berisi banyak panggilan ke prosedur yang dikompilasi secara asli dan Anda perlu melacak objek yang secara khusus memicu masalah ini, Anda cukup mencarinya dengan object_id
di database masing-masing.
Sekarang, saya tidak menyarankan menjalankan semua 400.000 panggilan dalam teks saat sesi aktif, atau mengaktifkan sesi ini di lingkungan produksi yang sangat bersamaan – jika Anda sering melakukan ini, ini dapat menyebabkan beberapa overhead yang signifikan. Jauh lebih baik Anda memeriksa aktivitas semacam ini di lingkungan pengembangan atau pementasan Anda, selama Anda dapat menerapkannya pada beban kerja yang tepat yang mencakup siklus bisnis penuh.
Kesimpulan
Saya benar-benar terkejut dengan fakta bahwa penamaan parameter – yang telah lama dianggap sebagai praktik terbaik – telah berubah menjadi praktik terburuk dengan prosedur tersimpan yang dikompilasi secara native. Dan diketahui oleh Microsoft sebagai masalah potensial yang cukup besar sehingga mereka membuat Acara yang Diperpanjang yang dirancang khusus untuk melacaknya. Jika Anda menggunakan OLTP Dalam Memori, ini adalah satu hal yang harus Anda perhatikan saat Anda mengembangkan prosedur tersimpan yang mendukung. Saya tahu saya pasti harus melepaskan pelatihan memori otot saya dari menggunakan parameter bernama.