索引分析

1.索引方法论

索引方法论包含3个步骤:监控分析和实施。这三步是循环运行的,先监控后分析最后实施,然后在监控在分析在实施,知道没有明显的和潜在的索引问题为止。

2.监控

常见的监控工具有下面的3种:

1.性能监视器

2.动态管理对象

3.SQL Trace

IF DB_ID(N'IndexDemo') IS NOT NULL
    DROP DATABASE IndexDemo
CREATE DATABASE IndexDemo
go
--创建一个表,用于存放对应计数器的值:
USE IndexDemo
GO
IF OBJECT_ID(N'IndexingCounters', 'U') IS NOT NULL
    DROP TABLE IndexingCounters
CREATE TABLE IndexingCounters
    (
      counter_id INT IDENTITY(1, 1) ,
      create_date DATETIME ,
      server_name VARCHAR(128) NOT NULL ,
      object_name VARCHAR(128) NOT NULL ,
      counter_name VARCHAR(128) NOT NULL ,
      instance_name VARCHAR(128) NULL ,
      Calculated_Counter_value FLOAT NULL ,
      CONSTRAINT PK_IndexingCounters PRIMARY KEY CLUSTERED ( counter_id )
    )
GO
CREATE NONCLUSTERED INDEX IX_IndexingCounters_CounterName
ON dbo.IndexingCounters (counter_name)
INCLUDE (create_date,server_name,object_name,Calculated_Counter_value)

 对于性能计数器,可以很频繁的查询,但是如果是监控索引相关的情况,则没必要太过密集,可以每5分钟执行 一次

IF DB_ID(N'IndexDemo') IS NOT NULL
    DROP DATABASE IndexDemo
CREATE DATABASE IndexDemo
go
--创建一个表,用于存放对应计数器的值:
USE IndexDemo
GO
IF OBJECT_ID(N'IndexingCounters', 'U') IS NOT NULL
    DROP TABLE IndexingCounters
CREATE TABLE IndexingCounters
    (
      counter_id INT IDENTITY(1, 1) ,
      create_date DATETIME ,
      server_name VARCHAR(128) NOT NULL ,
      object_name VARCHAR(128) NOT NULL ,
      counter_name VARCHAR(128) NOT NULL ,
      instance_name VARCHAR(128) NULL ,
      Calculated_Counter_value FLOAT NULL ,
      CONSTRAINT PK_IndexingCounters PRIMARY KEY CLUSTERED ( counter_id )
    )
GO
CREATE NONCLUSTERED INDEX IX_IndexingCounters_CounterName
ON dbo.IndexingCounters (counter_name)
INCLUDE (create_date,server_name,object_name,Calculated_Counter_value)


USE IndexDemo
IF OBJECT_ID('tempdb..#Delta') IS NOT  NULL
DROP TABLE #Delta
SELECT 
opc.object_name,
opc.counter_name,
opc.instance_name,
opc.cntr_value
INTO  #Delta
 FROM sys.dm_os_performance_counters opc
WHERE opc.counter_name LIKE '%/sec%' OR opc.counter_name LIKE '%KB/s%';
--等待特定时间,然后继续执行后续的语句
WAITFOR DELAY '00:00:10'

INSERT INTO dbo.IndexingCounters
        ( 
          create_date ,
          server_name ,
          object_name ,
          counter_name ,
          instance_name ,
          Calculated_Counter_value
        )
SELECT GETDATE(),
LEFT(opc.object_name,CHARINDEX(':',opc.object_name)-1),
SUBSTRING(opc.object_name,1+ CHARINDEX(':',opc.object_name),
LEN(opc.object_name)),
opc.counter_name,
opc.instance_name,
CASE  WHEN opc.cntr_type=537003264
THEN CONVERT (FLOAT,opc.cntr_value)/COALESCE(x.cntr_RatioBaseValue,-1)
WHEN b.cntr_value IS NOT NULL 
THEN (opc.cntr_value -b.cntr_value)/5.
ELSE opc.cntr_value 
END AS Calculated_Counter_value
FROM sys.dm_os_performance_counters opc 
LEFT OUTER JOIN 
(SELECT CASE cntr_value WHEN 0 THEN 1 ELSE cntr_value END AS cntr_RatioBaseValue,
SUBSTRING(counter_name,1,PATINDEX('% Base%',counter_name)) AS counter_name_fixed,
counter_name,
[object_name],
instance_name,
cntr_type
FROM sys.dm_os_performance_counters WHERE  cntr_type=1073939712
)
AS x ON opc.counter_name =x.counter_name_fixed AND opc.[object_name]=x.[object_name]
AND 
opc.instance_name=x.instance_name
LEFT OUTER JOIN #Delta b ON opc.counter_name=b.counter_name 
AND opc.object_name=b.counter_name
AND opc.instance_name=b.instance_name
WHERE opc.cntr_type <> 1073939712
AND ((opc.[object_name] LIKE '%Access Methods%' 
AND ( opc.counter_name LIKE 'Forwarded Records/sec'
OR opc.counter_name LIKE '%FreeSpace Scans/sec%'
OR opc.counter_name LIKE '%Full Scans/sec%'
OR opc.counter_name LIKE '%Index Searches/sec%'

OR opc.counter_name LIKE '%Page Splits/sec%'
))
OR
 (opc.[object_name] LIKE 'Buffer Manager'
AND (opc.counter_name LIKE '%Page life expectancy%'
OR opc.counter_name LIKE '%Page lookups/sec%'
))
OR (opc.[object_name] LIKE '%Locks%'
AND (opc.counter_name LIKE '%Lock Wait Time(ms)%'
OR opc.counter_name LIKE '%Lock Waits/sec%'
OR opc.counter_name LIKE '%Number of Deadlocks/sec%'
))
OR (opc.[object_name] LIKE '%SQL Statistics%' AND opc.counter_name LIKE '%Batch Requests/sec%'
  ))

 这是一次初始化的过程,随着对数据库的增加,可以从中获得更多的信息,并且可用其与初始值进行对比,创建一个表用于存放基准值,以便后续作为对比之用

  CREATE TABLE dbo.IndexingCountersBaseline
  (
  countter_baseline_id INT IDENTITY(1,1),
  start_date DATETIME,
  end_date DATETIME,
  server_name VARCHAR(128) NOT NULL,
  object_name VARCHAR(128) NOT NULL,
  counter_name VARCHAR(128) NOT NULL,
  instacn_name VARCHAR(128) NULL,
  minimum_counter_value FLOAT NULL,
  maximum_counter_value FLOAT NULL,
  average_counter_value FLOAT NULL,
  standard_deviation_counter_value FLOAT NULL,
  CONSTRAINT PK_IndexingCounterBaseline PRIMARY KEY CLUSTERED(countter_baseline_id)
  )

 在收集数据时,最好选择系统比较稳定的时期收集,并存在IndexingCounters表,然后汇总特定时间内的数据存入IndexingCounterBaseline。最后定期汇总数据,并检查数据的波动情况

DECLARE @StartDate DATETIME ='20120301',
  @EndDate DATETIME ='20120325'
  INSERT INTO dbo.IndexingCountersBaseline
          ( 
            start_date ,
            end_date ,
            server_name ,
            object_name ,
            counter_name ,
            instacn_name ,
            minimum_counter_value ,
            maximum_counter_value ,
            average_counter_value ,
            standard_deviation_counter_value
          )
 SELECT MIN(create_date),
 MAX(create_date),
 server_name,
 object_name,
 counter_name,
 instance_name,
 MIN(calculated_Counter_value),
 MAX(calculated_Counter_value),
 AVG(calculated_Counter_value),
--返回给定表达式中所有值的统计标准偏差。 STDEV(calculated_Counter_value) FROM dbo.IndexingCounters WHERE create_date BETWEEN @StartDate AND @EndDate GROUP BY server_name, object_name, counter_name, instance_name

 2.动态管理对象(DMO)

(1).索引使用统计信息

1) 创建一个快照表,存入当前DMO中的数据,作为初始化用

2)周期性的统计和对比快照表中最近的数据和快照中前一个时期的数据,然后把差异的数据插入到历史表中

 CREATE TABLE dbo.index_usage_stats_snapshot
 (
 snapshot_id int IDENTITY(1,1)
 create_date datetime,
 database_id INT NOT NULL,
 object_id INT NOT NULL,
 index_id INT NOT NULL,
 user_seeks BIGINT NOT NULL,
 user_scans BIGINT NOT NULL,
 user_lookups BIGINT NOT NULL,
 user_updates BIGINT NOT NULL,
 last_user_seek DATETIME,
 last_user_scan DATETIME,
 last_user_lookup DATETIME,
 lasr_user_update DATETIME,
 system_seeks  BIGINT NOT NULL,
 system_scans BIGINT NOT NULL,
 system_lookups BIGINT NOT NULL,
 system_updates BIGINT NOT NULL,
 last_system_seek DATETIME,
 last_system_scan DATETIME,
 last_system_lookup DATETIME,
 last_system_update DATETIME,
 CONSTRAINT PK_IndexUsageStatsSnapshot PRIMARY KEY CLUSTERED
 (snapshot_id),
 CONSTRAINT UQ_IndexUsageStatsSnapshot UNIQUE(create_date,database_id,object_id,index_id)
 );
 CREATE TABLE dbo.index_usage_stats_history
 (
history_id int IDENTITY(1,1)
 create_date datetime,
 database_id INT NOT NULL,
 object_id INT NOT NULL,
 index_id INT NOT NULL,
 user_seeks BIGINT NOT NULL,
 user_scans BIGINT NOT NULL,
 user_lookups BIGINT NOT NULL,
 user_updates BIGINT NOT NULL,
 last_user_seek DATETIME,
 last_user_scan DATETIME,
 last_user_lookup DATETIME,
 last_user_update DATETIME,
 system_seeks  BIGINT NOT NULL,
 system_scans BIGINT NOT NULL,
 system_lookups BIGINT NOT NULL,
 system_updates BIGINT NOT NULL,
 last_system_seek DATETIME,
 last_system_scan DATETIME,
 last_system_lookup DATETIME,
 last_system_update DATETIME,
 CONSTRAINT PK_IndexUsageStatsHistory PRIMARY KEY CLUSTERED(history_id)
 CONSTRAINT UQ_IndexUsageStatsHIstory UNIQUE (create_date,database_id,object_id,index_id)
 );

 历史表的数据来源于sys.dm_db_index_usage_stats(比如没4小时运行一次),然后存入历史表

  --创建完毕后初始化一次快照表

 INSERT INTO dbo.index_usage_stats_snapshot
 SELECT GETDATE(),
           database_id ,
           object_id ,
           index_id ,
           user_seeks ,
           user_scans ,
           user_lookups ,
           user_updates ,
           last_user_seek ,
           last_user_scan ,
           last_user_lookup ,
           last_user_update ,
           system_seeks ,
           system_scans ,
           system_lookups ,
           system_updates ,
           last_system_seek ,
           last_system_scan ,
           last_system_lookup ,
           last_system_update
         FROM sys.dm_db_index_usage_stats

 --比对结果存放在历史表中

		 
		 WITH IndexUsageCTE
		 AS (
		 SELECT DENSE_RANK() OVER (ORDER BY create_date DESC) AS HistoryID,
		 create_date,
		 database_id,object_id,
		 index_id,user_seeks,
		 user_scans,
		 user_lookups,
		 user_updates,
		 last_user_seek,
		 last_user_scan,
		 last_user_lookup,
		 last_user_update,
		 system_seeks,
		 system_scans,
		 system_lookups,
		 system_updates,
		 last_system_seek,
		 last_system_scan,
		 last_system_lookup,
		 last_system_update
		   FROM dbo.index_usage_stats_snapshot
		   )

		   --两次数据进行对比,然后插入历史表
		   INSERT INTO dbo.index_usage_stats_history
		   SELECT il.create_date ,
		          il.database_id ,
		            il. object_id ,
		             il.index_id ,
		            il. user_seeks - COALESCE(i2.user_seeks,0),
		            il. user_scans -COALESCE(i2.user_scans,0),
		            il. user_lookups -COALESCE(i2.user_lookups,0),
		            il. user_updates -COALESCE(i2.user_updates,0),
		            il. last_user_seek ,
		            il. last_user_scan ,
		            il. last_user_lookup ,
		             il.last_user_update ,
		             il.system_seeks -COALESCE(i2.system_seeks,0),
		            il. system_scans -COALESCE(i2.system_scans,0),
		            il. system_lookups -COALESCE(i2.system_lookups,0),
		            il. system_updates -COALESCE(i2.system_updates,0),
		            il. last_system_seek ,
		             il.last_system_scan ,
		            il. last_system_lookup ,
		            il. last_system_update
		           FROM IndexUsageCTE il
		  LEFT OUTER JOIN IndexUsageCTE i2
		  ON il.database_id=i2.database_id
		  AND il.object_id=i2.object_id
		  AND il.index_id=i2.index_id
		  AND i2.HistoryID=2
		  --检查没有小于0的数据
		  AND NOT (il.system_seeks-COALESCE(i2.system_seeks,0)<0
		  AND il.system_scans- COALESCE(i2.system_scans,0)<0
		  AND il.system_lookups-COALESCE(i2.system_lookups,0)<0
		  AND  il.system_updates-COALESCE(i2.user_updates,0)<0
		  AND il.user_seeks-COALESCE(i2.user_seeks,0)<0
		  AND il.user_scans-COALESCE(i2.user_scans,0)<0
		  AND il.user_lookups-COALESCE(i2.user_lookups,0)<0
		  AND il.system_updates-COALESCE(i2.user_updates,0)<0
		  )
		  WHERE 
		  il.HistoryID=1
		  AND
          (
		  il.system_seeks-COALESCE(i2.system_seeks,0)>0
		  OR il.system_scans-COALESCE(i2.system_scans,0)>0
		  OR il.system_lookups-COALESCE(i2.system_lookups,0)>0
		  OR il.system_updates-COALESCE(i2.system_updates,0)>0
		  OR il.user_seeks-COALESCE(i2.user_seeks,0)>0
		  OR il.user_scans-COALESCE(i2.user_scans,0)>0
		  OR il.user_lookups-COALESCE(i2.user_lookups,0)>0
		  OR il.user_updates-COALESCE(i2.user_updates,0)>0
		  )
         

		 --对比每个周期索引的使用情况
		 SELECT * FROM dbo.index_usage_stats_history ORDER BY object_id

  --对比每个周期索引的使用情况

   SELECT * FROM dbo.index_usage_stats_history ORDER BY object_id

 

通过上面的对比,可以看到每个索引在特定周期的使用情况,其中的具体数值并没有大意义,重点关注那些短期内次数增长非常迅速的部分

posted @ 2018-04-23 20:45  石shi  阅读(449)  评论(0编辑  收藏  举报