Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Menggunakan DBCC CLOENDATABASE dan Query Store untuk Pengujian

Musim panas lalu, setelah SP2 untuk SQL Server 2014 dirilis, saya menulis tentang penggunaan DBCC CLOENDATABASE untuk lebih dari sekadar menyelidiki masalah kinerja kueri. Komentar baru-baru ini pada posting oleh seorang pembaca membuat saya berpikir bahwa saya harus memperluas apa yang ada dalam pikiran saya tentang cara menggunakan database kloning untuk pengujian. Petrus menulis:

“Saya terutama seorang C# dev dan sementara saya menulis dan berurusan dengan T-SQL sepanjang waktu ketika harus melampaui SQL Server itu (hampir semua hal DBA, statistik, dan sejenisnya) saya tidak terlalu tahu banyak. . Bahkan tidak benar-benar tahu bagaimana saya akan menggunakan DB tiruan seperti ini untuk penyetelan kinerja”

Nah Peter, ini dia. Saya harap ini membantu!

Penyiapan

DBCC CLOENDATABASE tersedia di SQL Server 2016 SP1, jadi itulah yang akan kami gunakan untuk pengujian karena ini adalah rilis saat ini, dan karena saya dapat menggunakan Query Store untuk mengambil data saya. Untuk mempermudah hidup, saya membuat database untuk pengujian, daripada memulihkan sampel dari Microsoft.

GUNAKAN [master];GO DROP DATABASE JIKA ADA [CustomerDB], [CustomerDB_CLONE];GO /* Ubah lokasi file yang sesuai */ CREATE DATABASE [CustomerDB] ON PRIMARY ( NAME =N'CustomerDB', FILENAME =N' C:\Databases\CustomerDB.mdf' , SIZE =512MB , MAXSIZE =UNLIMITED, FILEGROWTH =65536KB ) LOG ON ( NAME =N'CustomerDB_log', FILENAME =N'C:\Databases\CustomerDB_log.ldf' , UKURAN =512MB , MAXSIZE =UNLIMITED , FILEGROWTH =65536KB );GO ALTER DATABASE [CustomerDB] SET RECOVERY SIMPLE;

Sekarang, buat tabel dan tambahkan beberapa data:

GUNAKAN [CustomerDB];BUKA TABEL [dbo].[Pelanggan]( [IDPelanggan] [int] NOT NULL, [FirstName] [nvarchar](64) NOT NULL, [LastName] [nvarchar](64) NOT NULL, [Email] [nvarchar](320) NOT NULL, [Aktif] [bit] NOT NULL DEFAULT 1, [Dibuat] [datetime] NOT NULL DEFAULT SYSDATETIME(), [Diperbarui] [datetime] NULL, CONSTRAINT [PK_Pelanggan] PRIMARY KEY CLUSTERED ([CustomerID]));GO /* Ini menambahkan 1.000.000 baris ke tabel; jangan ragu untuk menambahkan lebih sedikit*/INSERT dbo.Pelanggan DENGAN (TABLOCKX) (IDPelanggan, Nama Depan, Nama Belakang, EMail, [Aktif]) SELECT rn =ROW_NUMBER() OVER (ORDER BY n), fn, ln, em, FROM ( SELECT TOP (1000000) fn, ln, em, a =MAX(a), n =MAX(NEWID()) FROM ( SELECT fn, ln, em, a, r =ROW_NUMBER() OVER (PARTITION BY em ORDER BY em ) FROM ( SELECT TOP (20000000) fn =LEFT(o.name, 64), ln =LEFT(c.name, 64), em =LEFT(o.name, LEN(c.name)%5+1) + '.' + LEFT(c.name, LEN(o.name)%5+2) + '@' + KANAN(c.name, LEN(o.name + c.name)%12 + 1) + LEFT( RTRIM(CHECKSUM(NEWID())),3) + '.com', a =CASE WHEN c.name LIKE '%y%' THEN 0 ELSE 1 END DARI sys.all_objects AS o CROSS JOIN sys.all_columns AS c ORDER OLEH NEWID() ) AS x ) AS y WHERE r =1 GROUP BY fn, ln, em ORDER BY n ) AS z ORDER BY rn;GO CREATE NONCLUSTERED INDEX [PhoneBook_Customers] ON [dbo].[Pelanggan]([Nama Belakang] ,[NamaDepan])TERMASUK ([Email]);

Sekarang, kami akan mengaktifkan Query Store:

GUNAKAN [master];PERGI ALTER DATABASE [CustomerDB] SET QUERY_STORE =AKTIF; Ubah database [customerdb] atur query_store (operasi_mode =read_write, cleanup_policy =(stale_query_threshold_days =30), data_flush_interval_seconds =60, interval_length_minutes =5, max_storage_size_mb =256, 

Setelah database dibuat dan diisi, dan kami telah mengonfigurasi Query Store, kami akan membuat prosedur tersimpan untuk pengujian:

GUNAKAN [CustomerDB];PERGI PROSEDUR DROP JIKA ADA [dbo].[usp_GetCustomerInfo];GO CREATE OR ALTER PROCEDURE [dbo].[usp_GetCustomerInfo] (@LastName [nvarchar](64))SEBAGAI PILIH [IDPelanggan], [ FirstName], [LastName], [Email], CASE WHEN [Active] =1 THEN 'Active' ELSE 'Inactive' END [Status] FROM [dbo].[Pelanggan] WHERE [LastName] =@LastName;

Perhatikan:Saya menggunakan sintaks CREATE OR ALTER PROCEDURE baru yang keren yang tersedia di SP1.

Kami akan menjalankan prosedur tersimpan kami beberapa kali untuk mendapatkan beberapa data di Query Store. Saya telah menambahkan WITH RECOMPILE karena saya tahu bahwa kedua nilai input ini akan menghasilkan rencana yang berbeda, dan saya ingin memastikan untuk menangkap keduanya.

EXEC [dbo].[usp_GetCustomerInfo] 'name' WITH RECOMPILE;GOEXEC [dbo].[usp_GetCustomerInfo] 'query_cost' WITH RECOMPILE;

Jika kami melihat di Query Store, kami melihat satu kueri dari prosedur tersimpan kami, dan dua paket berbeda (masing-masing dengan plan_id sendiri). Jika ini adalah lingkungan produksi, kami akan memiliki lebih banyak data secara signifikan dalam hal statistik runtime (durasi, IO, informasi CPU) dan lebih banyak eksekusi. Meskipun demo kami memiliki lebih sedikit data, teorinya sama.

PILIH [qsq].[query_id], [qsp].[plan_id], [qsq].[object_id], [rs].[count_executions], DATEADD(MINUTE, -(DATEDIFF(MINUTE, GETDATE(), GETUTCDATE())), [qsp].[last_execution_time]) AS [LocalLastExecutionTime], [qst].[query_sql_text], ConvertedPlan =TRY_CONVERT(XML, [qsp].[query_plan])FROM [sys].[query_store_query] [ qsq] GABUNG [sys].[query_store_query_text] [qst] ON [qsq].[query_text_id] =[qst].[query_text_id]JOIN [sys].[query_store_plan] [qsp] ON [qsq].[query_id] =[ qsp].[query_id]JOIN [sys].[query_store_runtime_stats] [rs] ON [qsp].[plan_id] =[rs].[plan_id]WHERE [qsq].[object_id] =OBJECT_ID(N'usp_GetCustomerInfo'); 

Kueri Menyimpan data dari kueri prosedur tersimpan Kueri Menyimpan data setelah eksekusi prosedur tersimpan (query_id =1) dengan dua paket berbeda (plan_id =1, plan_id =2)

Rencana kueri untuk plan_id =1 (nilai masukan ='nama') Paket kueri untuk plan_id =2 (nilai masukan ='biaya_kueri')

Setelah kami memiliki informasi yang kami butuhkan di Query Store, kami dapat mengkloning database (data Query Store akan disertakan dalam kloning secara default):

DBCC CLOENDATABASE (N'CustomerDB', N'CustomerDB_CLONE');

Seperti yang saya sebutkan di posting CLOENDATABASE saya sebelumnya, database kloning dirancang untuk digunakan untuk dukungan produk untuk menguji masalah kinerja kueri. Dengan demikian, ini hanya-baca setelah dikloning. Kami akan melampaui apa yang saat ini dirancang untuk dilakukan oleh DBCC CLOENDATABASE, jadi sekali lagi, saya hanya ingin mengingatkan Anda tentang catatan ini dari dokumentasi Microsoft:

Basis data yang baru dibuat yang dihasilkan dari DBCC CLONEDATABASE tidak didukung untuk digunakan sebagai basis data produksi dan terutama ditujukan untuk pemecahan masalah dan tujuan diagnostik.

Untuk membuat perubahan apa pun untuk pengujian, saya perlu mengeluarkan database dari mode hanya-baca. Dan saya setuju dengan itu karena saya tidak berencana menggunakan ini untuk tujuan produksi. Jika basis data kloning ini berada dalam lingkungan produksi, saya sarankan Anda mencadangkannya dan memulihkannya di server pengembang atau pengujian dan lakukan pengujian Anda di sana. Saya tidak merekomendasikan pengujian dalam produksi, saya juga tidak menyarankan pengujian melawan instance produksi (bahkan dengan database yang berbeda).

/* Jadikan read write (cadangkan dan pulihkan di tempat lain sehingga Anda tidak bekerja dalam produksi)*/ALTER DATABASE [CustomerDB_CLONE] SET READ_WRITE WITH NO_WAIT;

Sekarang saya dalam status baca-tulis, saya dapat membuat perubahan, melakukan beberapa pengujian, dan menangkap metrik. Saya akan mulai dengan memverifikasi bahwa saya mendapatkan paket yang sama dengan yang saya lakukan sebelumnya (pengingat, Anda tidak akan melihat output apa pun di sini karena tidak ada data dalam database kloning):

/* verifikasi bahwa kami mendapatkan paket yang sama */USE [CustomerDB_CLONE];GOEXEC [dbo].[usp_GetCustomerInfo] 'name';GOEXEC [dbo].[usp_GetCustomerInfo] 'query_cost' WITH RECOMPILE;

Dalam memeriksa Query Store, Anda akan melihat nilai plan_id yang sama seperti sebelumnya. Ada beberapa baris untuk kombinasi query_id/plan_id karena interval waktu yang berbeda saat data diambil (ditentukan oleh setelan INTERVAL_LENGTH_MINUTES, yang kami setel ke 5).

PILIH [qsq].[query_id], [qsp].[plan_id], [qsq].[object_id], [rs].[count_executions], DATEADD(MINUTE, -(DATEDIFF(MINUTE, GETDATE(), GETUTCDATE())), [qsp].[last_execution_time]) AS [LocalLastExecutionTime], [rsi].[runtime_stats_interval_id], [rsi].[start_time], [rsi].[end_time], [qst].[query_sql_text] , ConvertedPlan =TRY_CONVERT(XML, [qsp].[query_plan])FROM [sys].[query_store_query] [qsq] GABUNG [sys].[query_store_query_text] [qst] ON [qsq].[query_text_id] =[qst]. [query_text_id]JOIN [sys].[query_store_plan] [qsp] ON [qsq].[query_id] =[qsp].[query_id]JOIN [sys].[query_store_runtime_stats] [rs] ON [qsp].[plan_id] =[rs].[plan_id]JOIN [sys].[query_store_runtime_stats_interval] [rsi] ON [rs].[runtime_stats_interval_id] =[rsi].[runtime_stats_interval_id]WHERE [qsq].[object_id] =OBJECT_ID(N'usp')GetCustomerInfo');PERGI

Kueri Menyimpan data setelah menjalankan prosedur tersimpan terhadap database kloning

Menguji Perubahan Kode

Untuk pengujian pertama kita, mari kita lihat bagaimana kita dapat menguji perubahan pada kode kita – khususnya, kita akan memodifikasi prosedur tersimpan untuk menghapus kolom [Aktif] dari daftar SELECT.

/* Ubah prosedur menggunakan CREATE OR ALTER (hapus [Aktif] dari kueri)*/CREATE OR ALTER PROCEDURE [dbo].[usp_GetCustomerInfo] (@LastName [nvarchar](64))AS SELECT [CustomerID], [FirstName ], [Nama Belakang], [Email] FROM [dbo].[Pelanggan] WHERE [Nama Belakang] =@Nama Belakang;

Jalankan kembali prosedur tersimpan:

EXEC [dbo].[usp_GetCustomerInfo] 'name' WITH RECOMPILE;GOEXEC [dbo].[usp_GetCustomerInfo] 'query_cost' WITH RECOMPILE;

Jika Anda kebetulan menampilkan rencana eksekusi yang sebenarnya, Anda akan melihat bahwa kedua kueri sekarang menggunakan rencana yang sama, karena kueri tersebut dicakup oleh indeks nonclustered yang kami buat pada awalnya.

Rencana eksekusi setelah mengubah prosedur tersimpan untuk menghapus [Aktif]

Kami dapat memverifikasi dengan Query Store, paket baru kami memiliki plan_id 41:

PILIH [qsq].[query_id], [qsp].[plan_id], [qsq].[object_id], [rs].[count_executions], DATEADD(MINUTE, -(DATEDIFF(MINUTE, GETDATE(), GETUTCDATE())), [qsp].[last_execution_time]) AS [LocalLastExecutionTime], [rsi].[runtime_stats_interval_id], [rsi].[start_time], [rsi].[end_time], [qst].[query_sql_text] , ConvertedPlan =TRY_CONVERT(XML, [qsp].[query_plan])FROM [sys].[query_store_query] [qsq] GABUNG [sys].[query_store_query_text] [qst] ON [qsq].[query_text_id] =[qst]. [query_text_id]JOIN [sys].[query_store_plan] [qsp] ON [qsq].[query_id] =[qsp].[query_id]JOIN [sys].[query_store_runtime_stats] [rs] ON [qsp].[plan_id] =[rs].[plan_id]JOIN [sys].[query_store_runtime_stats_interval] [rsi] ON [rs].[runtime_stats_interval_id] =[rsi].[runtime_stats_interval_id]WHERE [qsq].[object_id] =OBJECT_ID(N'usp')GetCustomerInfo');

Kueri Simpan data setelah mengubah prosedur tersimpan

Anda juga akan melihat di sini bahwa ada query_id baru (40). Query Store melakukan pencocokan tekstual, dan kami mengubah teks kueri, sehingga query_id baru dihasilkan. Perhatikan juga bahwa object_id tetap sama, karena digunakan menggunakan sintaks CREATE OR ALTER. Mari buat perubahan lain, tetapi gunakan DROP lalu CREATE OR ALTER.

/* Ubah prosedur menggunakan DROP lalu CREATE OR ALTER (gabungkan [FirstName] dan [LastName])*/DROP PROCEDURE IF EXISTS [dbo].[usp_GetCustomerInfo];GO CREATE OR ALTER PROCEDURE [dbo].[usp_GetCustomerInfo] (@Nama Belakang [nvarchar](64))SEBAGAI PILIH [IDPelanggan], RTRIM([NamaDepan]) + ' ' + RTRIM([NamaBelakang]), [Email] FROM [dbo].[Pelanggan] WHERE [Nama Belakang] =@ Nama Belakang;

Sekarang, kita jalankan kembali prosedurnya:

EXEC [dbo].[usp_GetCustomerInfo] 'name';GOEXEC [dbo].[usp_GetCustomerInfo] 'query_cost' WITH RECOMPILE;

Sekarang output dari Query Store menjadi lebih menarik, dan perhatikan bahwa predikat Query Store saya telah berubah menjadi WHERE [qsq].[object_id] <> 0.

PILIH [qsq].[query_id], [qsp].[plan_id], [qsq].[object_id], [rs].[count_executions], DATEADD(MINUTE, -(DATEDIFF(MINUTE, GETDATE(), GETUTCDATE())), [qsp].[last_execution_time]) AS [LocalLastExecutionTime], [rsi].[runtime_stats_interval_id], [rsi].[start_time], [rsi].[end_time], [qst].[query_sql_text] , ConvertedPlan =TRY_CONVERT(XML, [qsp].[query_plan])FROM [sys].[query_store_query] [qsq] GABUNG [sys].[query_store_query_text] [qst] ON [qsq].[query_text_id] =[qst]. [query_text_id]JOIN [sys].[query_store_plan] [qsp] ON [qsq].[query_id] =[qsp].[query_id]JOIN [sys].[query_store_runtime_stats] [rs] ON [qsp].[plan_id] =[rs].[plan_id]JOIN [sys].[query_store_runtime_stats_interval] [rsi] ON [rs].[runtime_stats_interval_id] =[rsi].[runtime_stats_interval_id]WHERE [qsq].[object_id] <> 0;

Kueri Simpan data setelah mengubah prosedur tersimpan menggunakan DROP lalu BUAT ATAU ubah

object_id telah berubah menjadi 661577395, dan saya memiliki query_id baru (42) karena teks kueri berubah, dan plan_id baru (43). Meskipun paket ini masih merupakan pencarian indeks dari indeks nonclustered saya, paket ini masih merupakan paket yang berbeda di Query Store. Pahami bahwa metode yang disarankan untuk mengubah objek saat Anda menggunakan Query Store adalah menggunakan ALTER daripada pola DROP dan CREATE. Hal ini berlaku dalam produksi, dan untuk pengujian seperti ini, karena Anda ingin menjaga object_id tetap sama untuk mempermudah menemukan perubahan.

Menguji Perubahan Indeks

Untuk Bagian II dari pengujian kami, daripada mengubah kueri, kami ingin melihat apakah kami dapat meningkatkan kinerja dengan mengubah indeks. Jadi kami akan mengubah prosedur tersimpan kembali ke kueri asli, lalu mengubah indeks.

BUAT ATAU UBAH PROSEDUR [dbo].[usp_GetCustomerInfo] (@LastName [nvarchar](64))AS SELECT [CustomerID], [FirstName], [LastName], [Email], CASE WHEN [Active] =1 THEN 'Aktif' LAINNYA 'Tidak aktif' END [Status] FROM [dbo].[Pelanggan] WHERE [Nama Belakang] =@Nama Belakang;GO /* Ubah indeks yang ada untuk menambahkan [Aktif] untuk menutupi kueri*/BUAT INDEKS TIDAK TERMASUK [Buku Telepon_Pelanggan] AKTIF [dbo].[Pelanggan]([Nama Belakang],[NamaDepan])TERMASUK ([Email], [Aktif])DENGAN (DROP_EXISTING=ON);

Karena saya menjatuhkan prosedur tersimpan asli, paket asli tidak lagi ada dalam cache. Jika saya membuat perubahan indeks ini terlebih dahulu, sebagai bagian dari pengujian, ingatlah bahwa kueri tidak akan secara otomatis menggunakan indeks baru kecuali saya memaksa kompilasi ulang. Saya dapat menggunakan sp_recompile pada objek, atau saya dapat terus menggunakan opsi WITH RECOMPILE pada prosedur untuk melihat bahwa saya mendapatkan paket yang sama dengan dua nilai yang berbeda (ingat saya awalnya memiliki dua paket yang berbeda). Saya tidak perlu WITH RECOMPILE karena paket tidak ada dalam cache, tetapi saya membiarkannya demi konsistensi.

EXEC [dbo].[usp_GetCustomerInfo] 'name' WITH RECOMPILE;GOEXEC [dbo].[usp_GetCustomerInfo] 'query_cost' WITH RECOMPILE;

Di dalam Query Store saya melihat query_id baru (karena object_id berbeda dari aslinya!) dan plan_id baru:

Kueri Simpan data setelah menambahkan indeks baru

Jika saya memeriksa paket, saya dapat melihat bahwa indeks yang dimodifikasi sedang digunakan.

Paket kueri setelah [Aktif] ditambahkan ke indeks (plan_id =50)

Dan sekarang saya memiliki rencana yang berbeda, saya dapat mengambil langkah lebih jauh dan mencoba mensimulasikan beban kerja produksi untuk memverifikasi bahwa dengan parameter input yang berbeda, prosedur tersimpan ini menghasilkan rencana yang sama dan menggunakan indeks baru. Ada peringatan di sini, meskipun. Anda mungkin telah memperhatikan peringatan pada operator Pencarian Indeks – ini terjadi karena tidak ada statistik pada kolom [Nama Belakang]. Saat kami membuat indeks dengan [Aktif] sebagai kolom yang disertakan, tabel dibaca untuk memperbarui statistik. Tidak ada data dalam tabel, maka kurangnya statistik. Ini jelas sesuatu yang perlu diingat dengan pengujian indeks. Ketika statistik tidak ada, pengoptimal akan menggunakan heuristik yang mungkin atau mungkin tidak meyakinkan pengoptimal untuk menggunakan rencana yang Anda harapkan.

Ringkasan

Saya penggemar berat DBCC CLOENDATABASE. Saya penggemar Query Store yang lebih besar. Saat Anda menggabungkan keduanya, Anda memiliki kemampuan hebat untuk pengujian cepat terhadap perubahan indeks dan kode. Dengan metode ini, Anda terutama melihat rencana eksekusi untuk memvalidasi peningkatan. Karena tidak ada data dalam database kloning, Anda tidak dapat menangkap penggunaan sumber daya dan statistik runtime untuk membuktikan atau menyangkal manfaat yang dirasakan dalam rencana eksekusi. Anda masih perlu memulihkan database dan mengujinya terhadap kumpulan data lengkap – dan Query Store masih bisa sangat membantu dalam menangkap data kuantitatif. Namun, untuk kasus di mana validasi paket sudah cukup, atau bagi Anda yang tidak melakukan pengujian apa pun saat ini, DBCC CLOENDATABASE menyediakan tombol mudah yang Anda cari. Query Store membuat proses lebih mudah.

Beberapa hal yang perlu diperhatikan:

Saya tidak menyarankan menggunakan WITH RECOMPILE saat memanggil prosedur tersimpan (atau mendeklarasikannya seperti itu - lihat posting Paul White). Saya menggunakan opsi ini untuk demo ini karena saya membuat prosedur tersimpan yang sensitif terhadap parameter, dan saya ingin memastikan nilai yang berbeda menghasilkan paket yang berbeda dan tidak menggunakan paket dari cache.

Menjalankan tes ini di SQL Server 2014 SP2 dengan DBCC CLOENDATABASE sangat mungkin, tetapi jelas ada pendekatan yang berbeda untuk menangkap kueri dan metrik, serta melihat kinerja. Jika Anda ingin melihat metodologi pengujian yang sama ini, tanpa Query Store, tinggalkan komentar dan beri tahu saya!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Parsing nilai default parameter menggunakan PowerShell – Bagian 1

  2. Memvisualisasikan Data

  3. Apa itu Percona DB

  4. Ambang Gabung Adaptif

  5. Salah satu cara untuk mendapatkan pencarian indeks untuk %wildcard terkemuka