有時候咱們會遇到,因爲統計信息不許確致使優化器生成了一個錯誤的執行計劃(或者這樣表達:一個較差的執行計劃),從而引發了系統性能問題。那麼若是咱們懷疑這個錯誤的執行計劃是因爲統計信息不許確引發的。那麼咱們如何判斷統計信息不許確呢?固然首先得去查看實際執行計劃中,統計信息的相關數據是否與實際狀況有較大的出入,下面咱們拋開這個大命題,僅僅從統計信息層面去查看統計信息的更新時間,統計信息的採樣行數、採樣比例等狀況。sql
1:首先,咱們要查查統計信息是何時更新的。數據庫
2:其次,咱們查看統計信息的採樣的百分比以及採樣信息:採樣選取的行數、自上次更新統計信息以來前導統計信息列(構建直方圖的列)的總修改次數。。。app
查看統計信息的最後更新時間。性能
方法1:優化
--查看統計信息的更新時間
DECLARE @TableName NVARCHAR(128);
SET @TableName = '[Maint].[JobHistoryDetails]';
SELECT @TableName AS Table_Name,
name AS Stats_Name ,
STATS_DATE(object_id, stats_id) AS Last_Stats_Update
FROM sys.stats
WHERE object_id = OBJECT_ID(@TableName)
ORDER BY 2 DESC;
如上所示,咱們經過這個腳本查看某個表全部的統計信息的最後一次更新時間。若是你須要查看某個具體的統計信息的最後更新時間,那麼在這個SQL的基礎上修改相關查詢條件便可。spa
方法2:3d
--查看統計信息的更新時間code
EXEC sp_autostats '[Maint].[JobHistoryDetails]';component
方法3:orm
還有一種方法能夠經過 sys.dm_db_stats_properties 返回統計信息的更新時間,不過這個DMF只有SQL Server 2008 R2 SP2這個版本以後的纔有。
列名 |
數據類型 |
Description |
object_id |
int |
要返回統計信息對象屬性的對象(表或索引視圖)的 ID。 |
stats_id |
int |
統計信息對象的 ID。 在表或索引視圖中是惟一的。 有關詳細信息,請參閱 sys.stats (Transact-SQL)。 |
last_updated |
datetime2 |
上次更新統計信息對象的日期和時間。 有關詳細信息,請參閱此頁中的備註部分。 |
rows |
bigint |
上次更新統計信息時表或索引視圖中的總行數。 若是篩選統計信息或者統計信息與篩選索引對應,該行數可能小於表中的行數。 |
rows_sampled |
bigint |
用於統計信息計算的抽樣總行數。 |
Step |
int |
直方圖中的值範圍數(步長)(Number of steps in the histogram)。 有關詳細信息,請參閱 DBCC SHOW_STATISTICS (Transact-SQL)。 |
unfiltered_rows |
bigint |
應用篩選表達式(用於篩選的統計信息)以前表中的總行數。 若是未篩選統計信息,則 unfiltered_rows 等於行列中返回的值。 |
modification_counter |
bigint |
自上次更新統計信息以來前導統計信息列(構建直方圖的列)的總修改次數。 |
persisted_sample_percent |
float |
持久樣本百分比用於未顯式指定採樣百分比的統計信息更新。 若是值爲零,則不爲此統計信息設置持久樣本百分比。 |
SELECT sch.name + '.' + so.name AS table_name
, so.object_id
, ss.name AS stat_name
, ds.stats_id
, ds.last_updated
, ds.rows
, ds.rows_sampled
, ds.rows_sampled*1.0/ds.rows *100 AS sample_rate
, ds.steps
, ds.unfiltered_rows
--, ds.persisted_sample_percent
, ds.modification_counter
FROM sys.stats ss
JOIN sys.objects so ON ss.object_id = so.object_id
JOIN sys.schemas sch ON so.schema_id = sch.schema_id
CROSS APPLY sys.dm_db_stats_properties(ss.object_id,ss.stats_id) ds
WHERE so.is_ms_shipped = 0
AND so.object_id NOT IN (
SELECT major_id
FROM sys.extended_properties (NOLOCK)
WHERE name = N'microsoft_database_tools_support' );
查看統計信息採樣的百分比
SELECT sch.name + '.' + so.name AS table_name
, so.object_id
, ss.name AS stat_name
, ds.stats_id
, ds.last_updated
, ds.rows
, ds.rows_sampled
, ds.steps
, ds.unfiltered_rows
, ds.modification_counter
FROM sys.stats ss
JOIN sys.objects so ON ss.object_id = so.object_id
JOIN sys.schemas sch ON so.schema_id = sch.schema_id
CROSS APPLY sys.dm_db_stats_properties(ss.object_id,ss.stats_id) ds
WHERE so.name = N'pbCutClothCost'
AND LEFT(ss.name, 4) != '_WA_';
如上截圖,索引IX_CutClothCost的統計信息有更新,是由於在執行上面腳本前,我更新了這個統計信息。經過rows與實際記錄數對比、 modification_counter信息,咱們從而有個大概的判斷,這些統計信息是否過期。是否採樣的比例過小。若是查看統計信息的採樣百分比,那麼能夠使用下面腳本。
SELECT sch.name + '.' + so.name AS table_name
, so.object_id
, ss.name AS stat_name
, ds.stats_id
, ds.last_updated
, ds.rows
, ds.rows_sampled
, ds.rows_sampled*1.0/ds.rows *100 AS sample_rate
, ds.steps
, ds.unfiltered_rows
, ds.modification_counter
FROM sys.stats ss
JOIN sys.objects so ON ss.object_id = so.object_id
JOIN sys.schemas sch ON so.schema_id = sch.schema_id
CROSS APPLY sys.dm_db_stats_properties(ss.object_id,ss.stats_id) ds
WHERE so.name = N'pbCutClothCost'
AND LEFT(ss.name, 4) != '_WA_';
查看整個數據庫的全部用戶表的採樣比例,能夠使用下面腳本
--適應於SQL Server 2016 (13.x) SP1 CU4以前的版本
SELECT sch.name + '.' + so.name AS table_name
, so.object_id
, ss.name AS stat_name
, ds.stats_id
, ds.last_updated
, ds.rows
, ds.rows_sampled
, ds.rows_sampled*1.0/ds.rows *100 AS sample_rate
, ds.steps
, ds.unfiltered_rows
--, ds.persisted_sample_percent
, ds.modification_counter
FROM sys.stats ss
JOIN sys.objects so ON ss.object_id = so.object_id
JOIN sys.schemas sch ON so.schema_id = sch.schema_id
CROSS APPLY sys.dm_db_stats_properties(ss.object_id,ss.stats_id) ds
WHERE so.is_ms_shipped = 0
AND so.object_id NOT IN (
SELECT major_id
FROM sys.extended_properties (NOLOCK)
WHERE name = N'microsoft_database_tools_support' );
固然也能夠使用DBCC SHOW_STATISTICS來查看統計信息的詳細信息。
DBCC SHOW_STATISTICS ('dbo.pbCutClothCost', IX_pbCutClothCost_N1)
查看統計信息是否須要更新
判斷統計信息是否過期的一個維度:統計信息最後更新的時間,經過時間維度(最後一次統計信息更新距今的時間)。這個對於下面的維度(修改的數據數量)而言,每每不是特別準確,可是也有參考意義。
SELECT
sch.name + '.' + so.name AS "Table",
ss.name AS"Statistic",
CASE
WHEN ss.auto_Created = 0 AND ss.user_created = 0 THEN 'Index Statistic'
WHEN ss.auto_created = 0 AND ss.user_created = 1 THEN 'USER Created'
WHEN ss.auto_created = 1 AND ss.user_created = 0 THEN 'Auto Created'
WHEN ss.AUTO_created = 1 AND ss.user_created = 1 THEN 'Not Possible'
END AS
"Statistic Type",
CASE
WHEN ss.has_filter = 1 THEN 'Filtered INDEX'
WHEN ss.has_filter = 0 THEN 'No Filter'
END AS "Filtered",
CASE
WHEN ss.filter_definition IS NULL THEN ''
WHEN ss.filter_definition IS NOT NULL THEN ss.filter_definition
END AS "Filter Definition",
sp.last_updated AS "Stats Last Updated",
sp.rows AS "Rows",
sp.rows_sampled AS "Rows Sampled",
sp.unfiltered_rows AS "Unfiltered Rows",
sp.modification_counter AS "Row Modifications",
sp.steps AS "Histogram Steps"
FROM sys.stats ss
JOIN sys.objects so ON ss.object_id = so.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) AS sp
WHERE so.TYPE = 'U'
AND sp.last_updated < GETDATE() - 7
ORDER BY sp.last_updated DESC;
之前收集過一個查詢過期的統計信息(忘記出自哪裏了),本身對腳本作過調整、修改,這個是經過自上次統計信息更新以來,變化的行數超過某個閥值來判斷統計信息是否過期。以下所示
Max(ApproximateRows) > 500 AND Max(RowModCtr) > (Max(ApproximateRows)*0.2 + 500 )
1:若是是SQL Server 2008 R2 SP2以上的版本,使用sys.dm_db_stats_properties的modification_counter字段值:自上次更新統計信息以來前導統計信息列(構建直方圖的列)的總修改次數
2:若是是SQL Server 2008 R2 SP2以前的版本,使用sysindexes的rowmodctr字段值:對自上次更新表的統計信息後插入、刪除或更新行的總數進行計數。
SET TRAN ISOLATION LEVEL READ UNCOMMITTED;
DECLARE @product_version NVARCHAR(128),
@db_version NVARCHAR(32) ,
@edition INT,
@small_edition INT,
@sql_script_index INT;
SET @product_version = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
--版本爲10.50.4000或高於這個版本使用sys.dm_db_stats_properties這個DMV,不然使用sysindexes中的rowmodctr字段
SELECT @db_version=
CASE
WHEN @product_version like '8%' THEN 'SQL2000'
WHEN @product_version like '9%' THEN 'SQL2005'
WHEN @product_version like '10.0%' THEN 'SQL2008'
WHEN @product_version like '10.5%' THEN 'SQL2008 R2'
WHEN @product_version like '11%' THEN 'SQL2012'
WHEN @product_version like '12%' THEN 'SQL2014'
WHEN @product_version like '13%' THEN 'SQL2016'
WHEN @product_version like '14%' THEN 'SQL2017'
ELSE 'unknown'
END
SET @edition= SUBSTRING(@db_version, 4, 4)
IF @edition <=2005
SET @sql_script_index=0;
ELSE IF @edition = 2008
IF @db_version ='SQL2008 R2'AND CAST(SUBSTRING(@product_version,7, 4) AS INT) >= 4000
SET @sql_script_index =1;
ELSE
SET @sql_script_index =0;
ELSE
SET @sql_script_index=1;
IF @sql_script_index = 0
BEGIN
PRINT '0'
EXEC sp_executesql N';WITH StatTables AS(
SELECT obj.schema_id AS ''schema_id''
,obj.name AS ''table_name''
,obj.object_id AS ''object_id''
,CASE INDEXPROPERTY(obj.object_id, dmv.name, ''IsStatistics'')
WHEN 0 THEN dmv.rows
ELSE (SELECT TOP 1 row_count FROM sys.dm_db_partition_stats ps (NOLOCK) WHERE ps.object_id=obj.object_id AND ps.index_id in (1,0))
END AS ''approximate_rows''
,dmv.rowmodctr AS ''row_mod_ctr''
FROM sys.objects obj (NOLOCK)
INNER JOIN sysindexes dmv (NOLOCK) ON obj.object_id = dmv.id
LEFT JOIN sys.indexes ind (NOLOCK) ON obj.object_id = ind.object_id AND obj.type in (''U'',''V'') AND ind.index_id = dmv.indid
WHERE obj.is_ms_shipped = 0 --object is not created by an internal sql server component
AND dmv.indid<>0
AND obj.object_id NOT IN (SELECT major_id FROM sys.extended_properties (NOLOCK) WHERE name = N''microsoft_database_tools_support'')
),
StatTableGrouped AS
(
SELECT
ROW_NUMBER() OVER(ORDER BY table_name) AS seq1
,ROW_NUMBER() OVER(ORDER BY table_name DESC) AS seq2
,table_name AS table_name
,CAST(MAX(approximate_rows) AS BIGINT) AS approximate_rows
,CAST(MAX(row_mod_ctr) AS BIGINT) AS row_mod_ctr
,schema_id
,object_id
FROM StatTables st
GROUP BY schema_id,object_id,table_name
HAVING (MAX(approximate_rows) > 500
AND MAX(row_mod_ctr) > (MAX(approximate_rows)*0.2 + 500 ))
)
SELECT
@@SERVERNAME AS instance_name
,seq1 + seq2 - 1 AS occurences_num
,SCHEMA_NAME(stg.schema_id) AS ''schema_name''
,stg.table_name
,CASE OBJECTPROPERTY(stg.object_id, ''TableHasClustIndex'')
WHEN 1 THEN ''Clustered''
WHEN 0 THEN ''Heap''
ELSE ''Indexed View''
END AS clustered_or_heap
,CASE OBJECTPROPERTY(stg.object_id, ''TableHasClustIndex'')
WHEN 0 THEN (SELECT COUNT(*) FROM sys.indexes i (NOLOCK) WHERE i.object_id= stg.object_id) - 1
ELSE (SELECT COUNT(*) FROM sys.indexes i (NOLOCK) WHERE i.object_id= stg.object_id)
END AS IndexCount
,(SELECT COUNT(*) FROM sys.columns c (NOLOCK) WHERE c.object_id = stg.object_id ) AS columns_count
,(SELECT COUNT(*) FROM sys.stats s (NOLOCK) WHERE s.object_id = stg.object_id) AS stats_count
,stg.approximate_rows
,stg.row_mod_ctr
,stg.schema_id
,stg.object_id
FROM StatTableGrouped stg';
END;
ELSE
BEGIN
EXEC sp_executesql N'
;WITH StatTables AS(
SELECT obj.schema_id AS schema_id
,obj.name AS table_name
,obj.object_id AS object_id
,ISNULL(sp.rows,0) AS approximate_rows
,ISNULL(sp.modification_counter,0) AS row_mod_ctr
FROM sys.objects obj (NOLOCK)
JOIN sys.stats st (NOLOCK) ON obj.object_id=st.object_id
CROSS APPLY sys.dm_db_stats_properties(obj.object_id, st.stats_id) AS sp
WHERE obj.is_ms_shipped = 0
AND st.stats_id<>0
AND obj.object_id NOT IN (
SELECT major_id FROM sys.extended_properties WITH(NOLOCK)
WHERE name = N''microsoft_database_tools_support'')
),
StatTableGrouped AS
(
SELECT
ROW_NUMBER() OVER(ORDER BY table_name) AS seq1,
ROW_NUMBER() OVER(ORDER BY table_name DESC) AS seq2,
table_name AS table_name,
CAST(MAX(approximate_rows) AS BIGINT) AS approximate_rows,
CAST(MAX(row_mod_ctr) AS BIGINT) AS row_mod_ctr,
COUNT(*) AS stats_count,
schema_id AS schema_id,
object_id AS object_id
FROM StatTables st
GROUP BY schema_id,object_id,table_name
HAVING (MAX(approximate_rows) > 500 AND Max(row_mod_ctr) > (Max(approximate_rows)*0.2 + 500 ))
)
SELECT
@@SERVERNAME AS instance_name
,seq1 + seq2 - 1 AS occurences_num
,SCHEMA_NAME(stg.schema_id) AS schema_name
,stg.table_name
,CASE OBJECTPROPERTY(stg.object_id, ''TableHasClustIndex'')
WHEN 1 THEN ''Clustered''
WHEN 0 THEN ''Heap''
ELSE ''Indexed View''
END AS clustered_or_heap
,CASE OBJECTPROPERTY(stg.object_id, ''TableHasClustIndex'')
WHEN 0 THEN (SELECT COUNT(*) FROM sys.indexes i WITH(NOLOCK) WHERE i.object_id= stg.object_id) - 1
ELSE (SELECT COUNT(*) FROM sys.indexes i WITH(NOLOCK) WHERE i.object_id= stg.object_id)
END AS IndexCount
,(SELECT COUNT(*) FROM sys.columns c (NOLOCK) WHERE c.object_id = stg.object_id ) AS columns_count
,stg.stats_count
,stg.approximate_rows
,stg.row_mod_ctr
,stg.schema_id
,stg.object_id
FROM StatTableGrouped stg';
END;
參考資料:
https://www.sqlskills.com/blogs/erin/new-statistics-dmf-in-sql-server-2008r2-sp2/