Saya telah membuat blog sebelumnya tentang mengapa saya tidak menyukai sp_updatestats. Saya baru-baru ini menemukan alasan lain bahwa itu bukan teman saya. TL;DR:Itu tidak memperbarui statistik pada tampilan yang diindeks. Sekarang, dokumentasi tidak mengklaim demikian, jadi tidak ada bug di sini. Dokumentasi MSDN dengan jelas menyatakan:
Menjalankan STATISTIK PEMBARUAN terhadap semua tabel yang ditentukan pengguna dan internal dalam database saat ini.Tapi… berapa banyak dari Anda yang berpikir tentang tampilan terindeks Anda dan bertanya-tanya apakah itu diperbarui? Saya akui saya tidak melakukannya. Saya lupa tentang tampilan yang diindeks, yang sangat disayangkan karena mereka bisa sangat kuat bila digunakan dengan tepat. Mereka juga bisa menjadi mimpi buruk untuk diurai saat Anda memecahkan masalah, tapi saya tidak akan membantah penggunaannya hari ini. Saya hanya ingin Anda menyadari bahwa mereka tidak diperbarui oleh sp_updatestats, dan lihat opsi apa yang Anda miliki.
Penyiapan
Karena World Series baru saja berakhir, kami akan menggunakan database Baseball untuk pengujian kami. Anda dapat mengunduhnya dari halaman Sumber Daya SQLskills. Setelah dipulihkan, kami akan membuat salinan tabel dbo.Players, bernama dbo.PlayerInfo, memuat beberapa ribu baris ke dalamnya, lalu membuat tampilan terindeks yang menggabungkan tabel baru kami ke tabel PitchingPost:
GUNAKAN [BaseballData];PERGI CREATE TABLE [dbo].[PlayerInfo]( [lahmanID] [int] NOT NULL, [playerID] [varchar](10) NULL DEFAULT (NULL), [managerID] [varchar]( 10) NULL DEFAULT (NULL), [hofID] [varchar](10) NULL DEFAULT (NULL), [birthYear] [int] NULL DEFAULT (NULL), [birthMonth] [int] NULL DEFAULT (NULL), [birthDay] [int] NULL DEFAULT (NULL), [birthCountry] [varchar](50) NULL DEFAULT (NULL), [birthState] [varchar](2) NULL DEFAULT (NULL), [birthCity] [varchar](50) NULL DEFAULT (NULL), [Tahun Kematian] [int] NULL DEFAULT (NULL), [Bulan Kematian] [int] NULL DEFAULT (NULL), [Hari Kematian] [int] NULL DEFAULT (NULL), [Negara Kematian] [varchar](50) NULL DEFAULT (NULL), [deathState] [varchar](2) NULL DEFAULT (NULL), [deathCity] [varchar](50) NULL DEFAULT (NULL), [nameFirst] [varchar](50) NULL DEFAULT (NULL), [nameLast] [varchar](50) NULL DEFAULT (NULL), [nameNote] [varchar](255) NULL DEFAULT (NULL), [nameGiven] [varchar](255) NULL DEFAULT (NULL), [nameNick] [varchar ](255) NULL DEFAULT (NULL), [berat] [int] NULL DEFAULT (NULL), [tinggi] [int] NULL, [kelelawar] [varchar](1) NULL DEFAULT (NULL), [melempar] [varchar](1) NULL DEFAULT (NULL), [debut] [varchar]( 10) NULL DEFAULT (NULL), [FinalGame] [varchar](10) NULL DEFAULT (NULL), [perguruan tinggi] [varchar](50) NULL DEFAULT (NULL), [lahman40ID] [varchar](9) NULL DEFAULT ( NULL), [lahman45ID] [varchar](9) NULL DEFAULT (NULL), [retroID] [varchar](9) NULL DEFAULT (NULL), [holtzID] [varchar](9) NULL DEFAULT (NULL), [bbrefID ] [varchar](9) NULL DEFAULT (NULL),PRIMARY KEY CLUSTERED ([lahmanID] ASC) ON [PRIMARY]) ON [PRIMARY];GO INSERT INTO [dbo].[PlayerInfo] ([lahmanID] ,[playerID] ,[managerID] ,[hofID] ,[Tahunkelahiran] ,[Bulan Lahir] ,[HariKelahiran] ,[Negara Kelahiran] ,[Negara Kelahiran] ,[Kota Kelahiran] ,[TahunKematian] ,[Bulan Kematian] ,[Hari Kematian] ,[Negara Kematian] ,[ deathState] ,[deathCity] ,[nameFirst] ,[nameLast] ,[nameNote] ,[nameGiven] ,[nameNick] ,[weight] ,[height] ,[kelelawar] ,[melempar] ,[debut] ,[finalGame] ,[college] ,[lahman40ID] ,[lahman45ID] ,[ retroID] ,[holtzID] ,[bbrefID])PILIH [lahmanID] ,[playerID] ,[managerID] ,[hofID] ,[birthYear] ,[birthMonth] ,[birthDay] ,[birthCountry] ,[birthState] ,[birthCity ] ,[deathYear] ,[deathMonth] ,[deathDay] ,[deathCountry] ,[deathState] ,[deathCity] ,[nameFirst] ,[nameLast] ,[nameNote] ,[nameGiven] ,[nameNick] ,[weight] , [tinggi] ,[kelelawar] ,[melempar] ,[debut] ,[finalGame] ,[perguruan tinggi] ,[lahman40ID] ,[lahman45ID] ,[retroI D] ,[holtzID] ,[bbrefID]FROM [dbo].[Players]WHERE [lahmanID] <=10000; BUAT TAMPILAN [PlayerPostSeason]DENGAN SCHEMABINDINGAS SELECT [p].[lahmanID], [p].[nameFirst], [p].[nameLast], [p].[debut], [p].[finalGame], [hal. ].[yearID], [pp].[round], [pp].[teamID], [pp].[W], [pp].[L], [pp].[G] FROM [dbo]. [PlayerInfo] [p] GABUNG [dbo].[Post Pitching] [pp] AKTIF [p].[playerID] =[pp].[playerID]; BUAT INDEKS CLUSTERED UNIK [CI_PlayerPostSeason] DI [PlayerPostSeason] ([lahmanID], [yearID], [round]); BUAT INDEKS NONCLUSTERED [NCI_PlayerPostSeason_Name] DI [PlayerPostSeason] ([nameFirst], [nameLast]);
Jika kita memeriksa statistik untuk indeks berkerumun dan tidak berkerumun, kita melihat mereka ada:
DBCC SHOW_STATISTICS ('PlayerPostSeason', CI_PlayerPostSeason) WITH STAT_HEADER;GODBCC SHOW_STATISTICS ('PlayerPostSeason', NCI_PlayerPostSeason_Name) DENGAN STAT_HEADER;GO
Statistik tampilan indeks setelah pembuatan awal
Sekarang kita akan memasukkan lebih banyak baris ke dalam PlayerInfo:
INSERT INTO [dbo].[PlayerInfo] ([lahmanID] ,[playerID] ,[managerID] ,[hofID] ,[birthYear] ,[birthMonth] ,[birthDay] ,[birthCountry] ,[birthState] ,[ kota kelahiran] ,[Tahun Kematian] ,[Bulan Kematian] ,[Hari Kematian] ,[Negara Kematian] ,[Negara Kematian] ,[Kota Kematian] ,[nameFirst] ,[nameLast] ,[nameNote] ,[nameGiven] ,[nameNick] ,[weight] ,[tinggi] ,[kelelawar] ,[melempar] ,[debut] ,[finalGame] ,[college] ,[lahman40ID] ,[lahman45ID] ,[retroID] ,[holtzID] ,[bbrefID])PILIH [lahmanID] , [playerID] ,[managerID] ,[hofID] ,[birthYear] ,[birthMonth] ,[birthDay] ,[birthCountry] ,[birthState] ,[birthCity] ,[DeathYear] ,[deathMonth] ,[deathDay] ,[deathCountry] ,[deathState] ,[deathCity] ,[nameFirst] ,[nameLast] ,[nameNote] ,[nameGiven] ,[nameNick] ,[weight] ,[ tinggi] ,[kelelawar] ,[melempar] ,[debut] ,[finalGame] ,[college] ,[lahman40ID] ,[lahman45ID] ,[retroID] ,[holtzID] ,[bbrefID]FROM [dbo].[Pemain] DIMANA [lahmanID]> 10.000;
Dan jika kita cek sys.dm_db_stats_properties, kita bisa melihat baris modifikasinya:
SELECT [sch].[name] AS [Schema], [so].[name] AS [ObjectName], [so].[type] AS [ObjectType], [ss].[name] AS [Statistic ], [sp].[last_updated] AS [StatsLastUpdated] , [sp].[rows] AS [RowsInTable] , [sp].[rows_sampled] AS [RowsSampled] , [sp].[modification_counter] AS [RowModifications]FROM [sys].[objects] [so]JOIN [sys].[stats] [ss] ON [so].[object_id] =[ss].[object_id]JOIN [sys].[schemas] [sch] ON [ so].[schema_id] =[sch].[schema_id]OUTER APPLY [sys].[dm_db_stats_properties]([so].[object_id], [ss].[stats_id]) spWHERE [so].[name] =' PlayerPostSeason';
Baris diubah dalam tampilan yang diindeks, melalui sys.dm_db_stats_properties
Dan iseng-iseng aja, kalau kita cek sys.sysindexes, kita juga bisa lihat modifikasinya di sana:
PILIH [jadi].[nama], [si].[nama], [si].[rowcnt], [si].[rowmodctr]FROM [sys].[sysindexes] [si]JOIN [sys] .[objects] [so] ON [si].[id] =[so].[object_id]WHERE [so].[name] ='PlayerPostSeason';
Baris diubah dalam tampilan yang diindeks, melalui sys.sysindexes
Sekarang sys.sysindexes sudah usang, tetapi jika Anda ingat dari posting saya sebelumnya, itulah yang digunakan sp_updatestats untuk melihat apa yang telah dimodifikasi. Tapi… daftar objek untuk sys.indexes didorong oleh kueri terhadap sys.objects, yang, jika Anda ingat, memfilter pada tabel pengguna ('U') dan tabel internal ('IT'). Itu tidak menyertakan tampilan ('V') dalam filter itu. Dengan demikian, ketika kita menjalankan sp_updatestats dan memeriksa output (tidak disertakan untuk singkatnya), tidak ada penyebutan tampilan PlayerPostSeason kita.
Oleh karena itu, jika Anda memiliki tampilan yang diindeks dan Anda mengandalkan sp_updatestats untuk memperbarui statistik Anda, statistik tampilan Anda tidak akan diperbarui. Namun, saya kira sebagian besar dari Anda mengaktifkan opsi Statistik Pembaruan Otomatis untuk database Anda. Ini bagus, karena dengan opsi ini, statistik tampilan akan diperbarui jika tidak valid. Kami tahu kami telah membuat lebih dari 2000 modifikasi pada indeks di PlayerPostSeason. Jika kami melakukan kueri dengan nama depan yang selektif, rencana kueri kami harus menggunakan indeks NCI_PlayerPostSeason_Name, dan karena statistik sudah kedaluwarsa, mereka harus diperbarui. Mari kita periksa:
SELECT *FROM [PlayerPostSeason]WHERE [nameFirst] ='Madison';GO
Rencana kueri dari SELECT terhadap indeks nonclustered
Kita dapat melihat dalam rencana bahwa indeks nonclustered NCI_PlayerPostSeason_Name digunakan, dan jika kita memeriksa statistik:
Statistik setelah pembaruan otomatis
Benar saja, statistik untuk indeks nonclustered telah diperbarui. Tapi tentu saja kami tidak ingin mengandalkan pembaruan otomatis untuk mengelola statistik, kami ingin proaktif. Kami memiliki dua opsi:
- Tugas Pemeliharaan
- Skrip Khusus
Tugas pemeliharaan statistik pembaruan dilakukan memperbarui statistik tampilan. Ini tidak secara khusus dipanggil di mana pun di UI, tetapi jika kami membuat rencana pemeliharaan dengan tugas statistik pembaruan dan menjalankannya, statistik untuk tampilan yang diindeks akan diperbarui. Kelemahan dari tugas pemeliharaan statistik pembaruan adalah bahwa ini adalah pendekatan palu godam. Ini memperbarui semua statistik, terlepas dari apakah itu diperlukan (hampir seburuk sp_updatestats). Saya lebih suka skrip khusus, di mana SQL Server hanya memperbarui apa yang telah dimodifikasi. Jika Anda tidak ingin menggulirkan skrip Anda sendiri, Anda dapat menggunakan skrip Ola Hallengren. Memutakhirkan statistik merupakan hal yang umum sebagai bagian dari pembangunan kembali dan penataan ulang indeks Anda. Misalnya, dengan skrip Ola dalam pekerjaan Agen SQL Anda akan memiliki:
sqlcmd -E -S $(ESCAPE_SQUOTE(SRVR)) -d master -Q "EXECUTE [dbo].[IndexOptimize] @Databases ='BaseballData', @FragmentationLow =NULL, @FragmentationMedium ='INDEX_REORGANIZE', @Fragmentation_REBUIL'INDEX_REBUILDEX_REBUILDEX_REBUIL ', @FragmentationLevel1 =5, @FragmentationLevel2 =30, @UpdateStatistics ='SEMUA', @OnlyModifiedStatistics ='Y', @LogToTable ='Y'" –bDengan opsi ini, jika statistik telah dimodifikasi, statistik akan diperbarui, dan jika kita memeriksa prosedur tersimpan [dbo].[IndexOptimize] kita dapat melihat di mana Ola memeriksa modifikasi:
-- Apakah data dalam statistik telah diubah sejak statistik terakhir diperbarui? JIKA @CurrentStatisticsID BUKAN NULL DAN @UpdateStatistics BUKAN NULL DAN @OnlyModifiedStatistics ='Y' MULAI SET @CurrentCommand10 ='' JIKA @LockTimeout BUKAN SET NULL @CurrentCommand10 ='SET LOCK_TIMEOUT ' + CAST(@Lonvarchar) + '; ' JIKA (@Versi>=10.504000 DAN @Versi <11) ATAU @Versi>=11.03000 MULAI SET @CurrentCommand10 =@CurrentCommand10 + 'GUNAKAN ' + QUOTENAME(@CurrentDatabaseName) + '; JIKA ADA(PILIH * FROM sys.dm_db_stats_properties (@ParamObjectID, @ParamStatisticsID) WHERE Modification_counter> 0) BEGIN SET @ParamStatisticsModified =1 END' END ELSE BEGIN SET @CurrentCommand10 =@ 'QUIFOROMISTS'( SELECT *FURrentCommand10 +' SELECT @CurrentDatabaseName) + '.sys.sysindexes sysindexes WHERE sysindexes.[id] =@ParamObjectID DAN sysindexes.[indid] =@ParamStatisticsID AND sysindexes.[rowmodctr] <> 0) MULAI SET @ParamStatisticsModified END =1 END>Untuk versi yang mendukung sys.dm_db_stats_properties DMF, Ola memeriksa statistik yang telah dimodifikasi, dan untuk versi yang tidak mendukung DMF sys.dm_db_stats_properties baru, tabel sistem sys.sysindexes diperiksa. Satu-satunya keluhan saya di sini adalah bahwa skrip berperilaku dengan cara yang sama seperti sp_updatestats:jika setidaknya satu baris telah diubah, statistik akan diperbarui.
Jika Anda tidak ingin menulis kode Anda sendiri untuk mengelola statistik, maka saya akan merekomendasikan untuk tetap menggunakan skrip Ola. Tetapi jika Anda ingin lebih menargetkan pembaruan Anda, saya sarankan menggunakan sys.dm_db_stats_properties. DMF ini hanya tersedia untuk SQL Server 2008R2 SP2 dan lebih tinggi, dan SQL Server 2012 SP1 dan lebih tinggi, jadi jika Anda menggunakan versi yang lebih rendah, Anda harus menggunakan sys.indexes. Tetapi bagi Anda yang memiliki akses ke sys.dm_db_stats_properties, berikut adalah kueri untuk membantu Anda memulai:
SELECT [sch].[name] AS [Schema], [so].[name] AS [ObjectName], [so].[type] AS [ObjectType], [ss].[name] AS [Statistic ], [sp].[last_updated] AS [StatsLastUpdated] , [sp].[rows] AS [RowsInTable] , [sp].[rows_sampled] AS [RowsSampled] , CAST(100 * [sp].[rows_sampled] / [sp].[rows] SEBAGAI DESIMAL (18, 2)) SEBAGAI [PercentSampled], [sp].[modification_counter] AS [RowModifications] , CAST(100 * [sp].[modification_counter] / [sp].[rows ] SEBAGAI DESIMAL(18, 2)) SEBAGAI [PercentChange]FROM [sys].[objects] AS [so]INNER JOIN [sys].[stats] AS [ss] ON [so].[object_id] =[ss] .[object_id]INNER JOIN [sys].[schemas] AS [sch] ON [so].[schema_id] =[sch].[schema_id]OUTER APPLY [sys].[dm_db_stats_properties]([so].[object_id] , [ss].[stats_id]) AS [sp]WHERE [so].[type] IN ('U','V')AND ((CAST(100 * [sp].[modification_counter] / [sp]. [baris] AS DECIMAL(18,2))>=10,0))ORDER BY CAST(100 * [sp].[modification_counter] / [sp].[rows] AS DECIMAL(18, 2)) DESC;Perhatikan bahwa dengan sys.objects kita memfilter pada tabel dan tampilan; Anda dapat mengubah ini untuk memasukkan tabel sistem. Anda kemudian dapat memodifikasi predikat untuk hanya mengambil baris berdasarkan persentase baris yang dimodifikasi, atau mungkin kombinasi persentase modifikasi dan jumlah baris (untuk tabel dengan jutaan atau miliaran baris, persentase itu mungkin lebih rendah daripada tabel kecil).
Ringkasan
Pesan bawa pulang di sini cukup jelas:Saya tidak menyarankan menggunakan sp_updatestats untuk mengelola statistik. Statistik diperbarui ketika satu atau beberapa baris telah berubah (yang merupakan ambang batas yang sangat rendah untuk memperbarui statistik) dan statistik untuk tampilan yang diindeks tidak diperbarui. Ini bukan metode yang komprehensif dan efisien untuk mengelola statistik…dan tugas statistik pembaruan dalam Rencana Pemeliharaan tidak jauh lebih baik. Ini memperbarui statistik tampilan yang diindeks, tetapi memperbarui setiap statistik, terlepas dari modifikasi. Skrip khusus benar-benar cara yang harus dilakukan, tetapi pahami bahwa skrip Ola Hallengren, jika Anda memperbarui berdasarkan modifikasi, juga memperbarui ketika hanya baris yang telah dimodifikasi (tetapi setidaknya mendapatkan tampilan yang diindeks). Pada akhirnya, untuk kontrol terbaik, lihat untuk menggulung skrip Anda sendiri untuk mengelola statistik. Saya telah memberi Anda kueri dasar untuk memulai. Jika Anda dapat meluangkan waktu beberapa jam untuk melatih penulisan T-SQL Anda dan kemudian mengujinya, Anda akan memiliki skrip khusus yang berfungsi untuk database Anda sebelum liburan tiba.