SQL Server 查找統計信息的採樣時間與採樣比例

有時候咱們會遇到,因爲統計信息不許確致使優化器生成了一個錯誤的執行計劃(或者這樣表達:一個較差的執行計劃),從而引發了系統性能問題。那麼若是咱們懷疑這個錯誤的執行計劃是因爲統計信息不許確引發的。那麼咱們如何判斷統計信息不許確呢?固然首先得去查看實際執行計劃中,統計信息的相關數據是否與實際狀況有較大的出入,下面咱們拋開這個大命題,僅僅從統計信息層面去查看統計信息的更新時間,統計信息的採樣行數、採樣比例等狀況。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;

 

 

clip_image001

 

 

如上所示,咱們經過這個腳本查看某個表全部的統計信息的最後一次更新時間。若是你須要查看某個具體的統計信息的最後更新時間,那麼在這個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

自上次更新統計信息以來前導統計信息列(構建直方圖的列)的總修改次數。

內存優化表: 正在啓動SQL Server 2016 (13.x)並在Azure SQL Database此列包含: 修改由於最後一個時間統計信息已更新或從新啓動數據庫的表的總次數。

persisted_sample_percent

float

持久樣本百分比用於未顯式指定採樣百分比的統計信息更新。 若是值爲零,則不爲此統計信息設置持久樣本百分比。

適用範圍: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' );

 

 

 

查看統計信息採樣的百分比

 

 

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_';

 

 

clip_image002

 

 

如上截圖,索引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/

相關文章
相關標籤/搜索