どのくらいの頻度で統計を更新する必要がありますか? 「めったにない」とは何ですか? 「頻度が高すぎる」はどのくらいの頻度ですか?
答えは、データベース、ユーザー、データなどによって「異なります」です。
したがって、私は統計がどのように見えるかを2つの表に記録しようとしました。はい、どうぞ:
DROP TABLE /*IF EXISTS */ dbo.dm_db_stats_histogram
DROP TABLE /*IF EXISTS */ dbo.dm_db_stats_properties
go
CREATE TABLE dbo.dm_db_stats_properties(
dm_db_stats_propertiesID INT IDENTITY(1,1) NOT NULL constraint PK_dm_db_stats_properties PRIMARY KEY CLUSTERED,
DatabaseId INT NOT NULL,
object_id int NOT NULL,
stats_id int NOT NULL,
last_updated DATETIME2 NOT NULL,
rows BIGINT NOT NULL,
rows_sampled BIGINT NOT NULL,
steps int NOT NULL,
unfiltered_rows BIGINT NOT NULL,
modification_counter BIGINT NOT NULL,
persisted_sample_percent FLOAT NULL
, SampleDate DATETIME2 NOT NULL CONSTRAINT df_dm_db_stats_properties_SampleDate DEFAULT SYSUTCDATETIME()
)
GO
ALTER TABLE dbo.dm_db_stats_properties ADD StatsName NVARCHAR(128) NOT NULL CONSTRAINT df_dm_db_stats_properties_StatsName DEFAULT ('')
GO
CREATE TABLE dbo.dm_db_stats_histogram(
dm_db_stats_histogramID INT IDENTITY(1,1) NOT NULL constraint PK_dm_db_stats_histogram PRIMARY KEY CLUSTERED,
dm_db_stats_propertiesID INT NOT NULL,
object_id int NOT NULL,
stats_id int NOT NULL,
step_number int NOT NULL,
range_high_key sql_variant NOT NULL,
range_rows real NOT NULL,
equal_rows real NOT NULL,
distinct_range_rows bigint NOT NULL,
average_range_rows REAL NOT NULL
)
go
ALTER TABLE dbo.dm_db_stats_histogram ADD CONSTRAINT fk_dm_db_stats_properties FOREIGN KEY(dm_db_stats_propertiesID) REFERENCES dbo.dm_db_stats_properties(dm_db_stats_propertiesID)
ALTER TABLE dbo.dm_db_stats_histogram ALTER COLUMN range_high_key SQL_VARIANT NULL
GO
そして、ここに私が統計を記録するために使用するコードがあります:
SET NOCOUNT ON
BEGIN TRY
DROP TABLE #Stat_Header
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE #Stat_Header (Name sysname, Updated DATETIME, Rows BIGINT, Rows_Sampled BIGINT, Steps SMALLINT, Density REAL, AverageKeyLength INT, StringIndex varchar(10)
, FilterExpression varchar(8000), unfiltered_rows bigint, persisted_sample_percent float)
BEGIN TRY
DROP TABLE #Histogram
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE #Histogram (Step_Number INT IDENTITY(1,1), range_high_key SQL_VARIANT, range_rows REAL NOT NULL, equal_rows REAL NOT NULL, distinct_range_rows BIGINT NOT NULL, average_range_rows REAL NOT NULL)
DECLARE TableCursor CURSOR LOCAL STATIC FOR
SELECT t.name AS TableName, sc.name AS SchemaName
FROM sys.tables t
INNER JOIN sys.schemas sc ON sc.schema_id = t.schema_id
ORDER BY sc.name, t.name
DECLARE @sql NVARCHAR(MAX) = '', @TableName VARCHAR(100), @SchemaName VARCHAR(100), @loopCounter INT =0
SELECT GETDATE() AS StartDate
OPEN TableCursor
WHILE 1 =1 BEGIN
FETCH TableCursor INTO @TableName, @SchemaName
IF @@fetch_status <> 0 BREAK
SELECT @sql = 'declare @Scope_Identity int = 0, @RowCount int
SET NOCOUNT ON' + CHAR(13)
SELECT @sql += '
TRUNCATE TABLE #Stat_Header
TRUNCATE TABLE #Histogram
INSERT INTO #Stat_Header(Name, Updated, Rows, Rows_Sampled, Steps, Density, AverageKeyLength, StringIndex, FilterExpression, unfiltered_rows/*, persisted_sample_percent*/)
exec (''DBCC SHOW_STATISTICS ("' + @SchemaName + '.' + @TableName + '", "' + s.name +'") with STAT_HEADER'')
INSERT INTO dbo.dm_db_stats_properties(databaseid, object_id, stats_id, last_updated, rows, rows_sampled, steps, unfiltered_rows, modification_counter, persisted_sample_percent, SampleDate, StatsName)
SELECT db_id(), ' + LTRIM(t.object_id) + ', ' + LTRIM(s.stats_id) +', coalesce(sh.Updated, ''2000-01-01''), isnull(sh.rows,0), isnull(sh.Rows_Sampled,0), isnull(sh.steps,0), isnull(sh.unfiltered_rows,0), 0, sh.persisted_sample_percent, cast(''' + LTRIM(SYSUTCDATETIME()) + ''' as datetime2(7)), ''' + s.name + '''
FROM #Stat_Header sh
LEFT JOIN dbo.dm_db_stats_properties sp ON sp.object_id=' + LTRIM(t.object_id) + ' AND sp.stats_id=' + LTRIM(s.stats_id) + ' AND sh.Updated=sp.last_updated
WHERE sp.dm_db_stats_propertiesID IS NULL
SELECT @Scope_Identity = SCOPE_IDENTITY(), @RowCount=@@ROWCOUNT
IF @RowCount>0 BEGIN
--raiserror (''here'', 10, 1) with nowait
INSERT INTO #Histogram(range_high_key, range_rows, equal_rows, distinct_range_rows, average_range_rows)
exec (''DBCC SHOW_STATISTICS ("' + @SchemaName + '.' + @TableName + '", "' + s.name +'") with HISTOGRAM'')
INSERT INTO dbo.dm_db_stats_histogram(dm_db_stats_propertiesID, object_id, stats_id, step_number, range_high_key, range_rows, equal_rows, distinct_range_rows, average_range_rows)
SELECT @Scope_Identity, ' + LTRIM(t.object_id) + ', ' + LTRIM(s.stats_id) +', h.Step_Number, h.range_high_key, h.range_rows, h.equal_rows, h.distinct_range_rows, h.average_range_rows
FROM #Histogram h
END
raiserror (''table = ' + @TableName + ', ' + s.name + ', rc= %i '', 10, 1, @RowCount) with nowait
waitfor delay ''00:00:01''
'
FROM sys.stats AS s
INNER JOIN sys.tables t ON t.object_id = s.object_id
INNER JOIN sys.schemas sc ON sc.schema_id = t.schema_id
WHERE t.name=@TableName
AND sc.name = @SchemaName
IF @loopCounter < 1 EXEC dbo.LongPrint @String=@sql
SET @loopCounter +=1
EXEC sp_executesql @sql
--BREAK
END
DEALLOCATE TableCursor
SELECT GETDATE() AS StopDate
SQL Server 2016 + 17で新しいDMVを使用するソリューションもあります
exec sp_foreachdb @command = N'
use ?
DECLARE @SampleDate DATETIME2 = SYSUTCDATETIME()
INSERT INTO master.dbo.dm_db_stats_properties(DatabaseID, object_id, stats_id, last_updated, rows, rows_sampled, steps, unfiltered_rows, modification_counter, persisted_sample_percent, SampleDate, StatsName)
SELECT db_id() as DatabaseID, s.object_id, s.stats_id, sp.last_updated, sp.rows, sp.rows_sampled, sp.steps, sp.unfiltered_rows, sp.modification_counter, sp.persisted_sample_percent, @SampleDate, s.name
FROM ?.sys.stats AS s
INNER JOIN ?.sys.tables t ON t.object_id = s.object_id
INNER JOIN ?.sys.schemas sc ON sc.schema_id = t.schema_id
CROSS APPLY ?.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp
LEFT JOIN master.dbo.dm_db_stats_properties T1 ON T1.object_id = s.object_id AND T1.stats_id = s.stats_id AND T1.last_updated=sp.last_updated
WHERE sp.last_updated IS NOT NULL
AND T1.last_updated IS NULL
select @@rowcount as r1
INSERT INTO master.dbo.dm_db_stats_histogram(dm_db_stats_propertiesID, object_id, stats_id, step_number, range_high_key, range_rows, equal_rows, distinct_range_rows, average_range_rows)
SELECT sp.dm_db_stats_propertiesID, sp.object_id, sp.stats_id, hist.step_number, hist.range_high_key, hist.range_rows, hist.equal_rows, hist.distinct_range_rows, hist.average_range_rows
FROM master.dbo.dm_db_stats_properties sp
CROSS APPLY ?.sys.dm_db_stats_histogram(sp.[object_id], sp.stats_id) AS hist
WHERE sp.SampleDate = @SampleDate
select @@rowcount as r2
', @exclude_list='tempdb, model', @print_dbname=1
今、私が収集したデータに基づいて、クエリをどのように書くのですか?
a)サンプリング期間中に大きく変化しない(変化しない)テーブル、インデックス、列はどれですか?
b)WITH FULLSCANコマンドの恩恵を受けるテーブル、インデックス、列はどれですか?
b)WITH FULLSCANコマンドの恩恵を受けるテーブル、インデックス、列はどれですか?
私の観点からは、あなたはその質問に答えるための適切なデータを収集していません。データベースの統計を分析するだけで改善できる点を探している場合、FULLSCAN
の代わりにサンプリングされた統計を使用することで引き起こされる可能性のあるクエリパフォーマンスの問題は2つしか考えられません。
密度は少なくとも1桁ずれています。
一部のデータ分布は、サンプリングされたデータをヒストグラムに変換するときにSQL Serverが行ういくつかの仮定に適していません。このような状況では、密度が10倍、100倍、またはそれ以上も低下する可能性があります。これは、統計オブジェクトで密度ベクトルを使用するクエリのパフォーマンスの問題を引き起こす可能性があります。
サンプリングされたすべての統計の密度情報を保存し、FULLSCANを使用して関連するすべての列の統計を収集し、2つの結果セット間の密度を比較することで、考えられる問題を検索できます。不正確すぎるものはすべて、統計を完全に収集することのメリットを享受するための候補です。
クエリは 昇順キーの問題 に対して脆弱です。
SQL Server 2008がタグとしてリストされているので、これはまだあなたに関係があるかもしれません。行が挿入された日時を格納する列について考えます。非常に最近のデータを探してその列をフィルターするクエリがある場合、それらはヒストグラムの外のデータを検索している可能性があります。レガシーCEを使用すると、カーディナリティの見積もりが非常に低くなり、クエリのパフォーマンスの問題が発生する可能性があります。
これはFULLSCAN統計で対処できますが、私には少しやり過ぎに感じられます。関連するデータタイプ(VARCHARの昇順キーについて心配する必要がないことを願う)のすべての統計に関する統計を数回収集し、最大の高キーがどのように変化するかを確認できます。
上記の両方の問題について、サンプリングされた統計だけを見てもプログラムでそれらを見つける方法は考えられません。そのため、質問に答えるための適切なデータを収集していないとおっしゃいました。
私の意見に関心がある場合、統計のメンテナンスを本当に最小限に抑える方法は、十分に実行されていないクエリのワークロードを分析し、慎重に根本原因分析を行って、統計の問題が原因となっている時期を特定し、正確なタイプの統計を特定することです問題、そして最後にそれに応じて統計メンテナンスジョブを調整します。
私はこれにかなりの時間を費やしましたが、これまでのところ最も有用なのは、ステップ数が最も大きいテーブル/列/インデックスを表示するクエリです。
drop table /* if exists */ #inv
create table #inv(dm_db_stats_propertiesID int, DatabaseID int, object_id int, stats_id int, Steps varchar(100), dm_db_stats_propertiesIDs varchar(400)
, DatabaseName sysname, StatsName varchar(100) null, TableName varchar(100) null, ColumnName varchar(100) null, minSteps int, maxSteps int)
insert into #inv( dm_db_stats_propertiesID, DatabaseID, object_id, stats_id, Steps, dm_db_stats_propertiesIDs, DatabaseName, StatsName, minSteps, maxSteps)
SELECT dm_db_stats_propertiesID, DatabaseID, object_id, stats_id, Steps, dm_db_stats_propertiesIDs, a.Name as DatabaseName, StatsName, minSteps, maxSteps
from (
SELECT top (10000) L.Name, sp.*
, stuff((select ', ' +ltrim(steps) from dbo.dm_db_stats_properties s2 where s2.DatabaseID=sp.DatabaseID and s2.object_id = sp.object_id and s2.stats_id=sp.stats_id for xml path('')),1,2,'') as Steps2
, stuff((select ', ' + ltrim(dm_db_stats_propertiesID) from dbo.dm_db_stats_properties s2 where s2.DatabaseID=sp.DatabaseID and s2.object_id = sp.object_id and s2.stats_id=sp.stats_id for xml path('')),1,2,'') as dm_db_stats_propertiesIDs
, (select count(*) as rowcnt from dbo.dm_db_stats_properties s2 where s2.DatabaseID=sp.DatabaseID and s2.object_id = sp.object_id and s2.stats_id=sp.stats_id ) as rowcnt
, (select min(Steps) as minSteps from dbo.dm_db_stats_properties s2 where s2.DatabaseID=sp.DatabaseID and s2.object_id = sp.object_id and s2.stats_id=sp.stats_id ) as minSteps
, (select max(Steps) as maxSteps from dbo.dm_db_stats_properties s2 where s2.DatabaseID=sp.DatabaseID and s2.object_id = sp.object_id and s2.stats_id=sp.stats_id ) as maxSteps
FROM dbo.dm_db_stats_properties sp
inner join #List L on sp.DatabaseID=L.DatabaseID and sp.SampleDate>=L.SampleDate
) as a
where a.Steps2 like '%,%' --',%'
exec sp_foreachdb 'update #inv set TableName = t.name /* select * */ from #inv i inner join ?.sys.tables t on i.object_id = t.object_id and (''['' + i.DatabaseName + '']'') = ''?'''
exec sp_foreachdb 'update #inv set ColumnName = c.name from #inv i
inner join ?.sys.columns c on i.object_id=c.object_id and c.column_id = convert(int, convert(varbinary, SUBSTRING(i.StatsName, 9, 8),2)) and (''['' + i.DatabaseName + '']'') = ''?''
where i.StatsName like ''_WA_Sys%''
'
select * , (0.0+maxSteps-minSteps)/maxSteps * 100 as PctChange
from #inv
where minSteps <> maxSteps
and DatabaseName <> 'master'
AND TableName NOT LIKE 'dm_db_stats%'
order by PctChange desc
私が望んでいたものとはほとんど違います。