数据库数据变大会导致查询慢
Why is my database so slow? This query used to be so much faster. Why does it take so long to rebuild my index? How come it was fine last month? Every day I am asked these types of questions by clients. Every day! A lot of database developers and application developers do not realize that indexes are ever changing entities within your database or rather they need to be monitored closely and managed periodically to remain efficient. I cannot even count the times someone tells me “but we have index’s on this or that column and it was fine last month” and so on. All while they fail to realize or even tell me that the database just took on, updated or deleted 1,000,000 records for example, which would definitely change the footprint of the data, making the index’s unsound or in need of help. Even adding 50 new users that use the data differently could require new indexes. That being said, I decided to automate a quick and easy data gathering and reporting job that helps to answer these questions. Most of the time query performance questions can be answered by determining the fragmentation levels of index’s, if there are missing index’s, duplicate index’s, unused index’s and what are the heavy hitters in regards to queries and are queries running in memory or to disk and how many executions. My favorite thing to do with SQL Server is automate, automate and automate the tasks that are asked of me over and over.
为什么我的数据库这么慢? 这个查询过去要快得多。 为什么重建索引需要这么长时间? 上个月还好吗? 客户每天都会问我这些类型的问题。 每天! 许多数据库开发人员和应用程序开发人员都没有意识到索引是数据库中不断变化的实体,或者需要对其进行密切监视和定期管理以保持效率。 我什至无法计算有人告诉我“但我们在该列上具有索引,上个月还不错”的次数等等。 他们全都无法意识到甚至告诉我,例如,数据库仅接受,更新或删除了1,000,000条记录,这肯定会改变数据的覆盖范围,从而使索引不可靠或需要帮助。 即使添加50个使用不同数据的新用户,也可能需要新索引。 话虽这么说,我决定自动化快速而轻松的数据收集和报告工作,以帮助回答这些问题。 在大多数情况下,查询性能问题可以通过确定索引的碎片级别来回答,如果缺少索引,重复索引,未使用的索引以及在查询方面的重击者是什么,以及在内存或磁盘中运行的查询以及如何运行许多处决。 我最喜欢的与SQL Server关联的事情是自动化,自动化和自动化一遍又一遍询问我的任务。
This simple job includes 9 tables used for collecting data, 7 reports, and one SQL agent job to tie it all together and do a cleanup step and finally kick off the tasks. Steps 1-5 below will show how to individually set up the job however, you can also just run “scripts 1-setup tables.sql” and “2-sgent job.sql” that are included here, however, make sure you update the variables with your information prior to running.
这个简单的工作包括9个用于收集数据的表,7个报告,以及一个SQL代理工作,将它们绑在一起并执行清理步骤,最后启动任务。 下面的步骤1-5将显示如何单独设置作业,但是,您也可以只运行此处包含的“ scripts 1-setup table.sql”和“ 2-sgent job.sql”,但是,请确保您进行更新在运行之前将变量与您的信息一起使用。
先决条件: ( Prerequisites: )
- I have created a small database on each server that I monitor called DBA_Monitoring, this is where I do most of my DBA tasks. This is optional of course; I like to keep my dba monitoring separate from other production databases. You can call yours whatever you like just be sure to change it in the scripts provided before you run them. 我在我监控的每个服务器上都创建了一个名为DBA_Monitoring的小型数据库,这是我执行大多数DBA任务的地方。 当然这是可选的。 我希望将dba监控与其他生产数据库分开。 您可以随心所欲地调用自己的脚本,只需在运行它们之前确保在提供的脚本中对其进行了更改。
- You must have dbMail enabled on your server that you will be monitoring and note your mail profile name as this will be used to send reports via email. 您必须在要监视的服务器上启用dbMail,并记下您的邮件配置文件名称,因为该名称将用于通过电子邮件发送报告。
创建收集包 ( Creating the Collection Package )
步骤1:创建表 ( Step 1: Create the tables )
I have created 9 tables in DBA_Monitoring to house each type of data collected in this job. 7 are for data collection and 2 are staging tables. These will need to be generated only once and will be truncated between data collection runs. Copy / Paste the scripts below and run them in your DBA_Monitoring database.
我在DBA_Monitoring中创建了9个表来容纳此作业中收集的每种数据类型。 7个用于数据收集,而2个是临时表。 这些将仅需要生成一次,并将在数据收集运行之间被截断。 复制/粘贴下面的脚本,然后在DBA_Monitoring数据库中运行它们。
--Create the tables
USE [DBA_Monitoring]
GO
-- used to collect the first round of index read counts
CREATE TABLE [dbo].[IndexOverviewReadsWithCounts_stage](
[ServerName] [nvarchar](128) NULL,
[Database] [nvarchar](128) NULL,
[Object] [nvarchar](128) NULL,
[execution_count] [bigint] NOT NULL,
[total_logical_reads] [bigint] NOT NULL,
[total_physical_reads] [bigint] NOT NULL
) ON [PRIMARY]
GO
--used to collect the first round of index read counts and queries
CREATE TABLE [dbo].[IndexOverviewReadsWithQuery_stage](
[ServerName] [nvarchar](128) NULL,
[Database] [nvarchar](128) NULL,
[Object] [nvarchar](128) NULL,
[execution_count] [bigint] NOT NULL,
[total_logical_reads] [bigint] NOT NULL,
[total_physical_reads] [bigint] NOT NULL,
[Statement] [nvarchar](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
--used as final index reads with counts where database name is updated to the monitored database
CREATE TABLE [dbo].[IndexOverviewReadsWithCounts](
[ServerName] [nvarchar](128) NULL,
[Database] [nvarchar](128) NULL,
[Object] [nvarchar](128) NULL,
[execution_count] [bigint] NOT NULL,
[total_logical_reads] [bigint] NOT NULL,
[total_physical_reads] [bigint] NOT NULL
) ON [PRIMARY]
GO
----used as final index reads with statements where database name is updated to the monitored database
CREATE TABLE [dbo].[IndexOverviewReadsWithQuery](
[ServerName] [nvarchar](128) NULL,
[Database] [nvarchar](128) NULL,
[Object] [nvarchar](128) NULL,
[execution_count] [bigint] NOT NULL,
[total_logical_reads] [bigint] NOT NULL,
[total_physical_reads] [bigint] NOT NULL,
[Statement] [nvarchar](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
--used to collect duplicate index
CREATE TABLE [dbo].[IndexOverview_DuplicateIndex](
[ServerName] [nvarchar](128) NULL,
[DBName] [varchar](100) NOT NULL,
[TableName] [sysname] NOT NULL,
[IndexName] [sysname] NULL,
[OverLappingIndex] [sysname] NULL,
[Col1] [nvarchar](128) NULL,
[Col2] [nvarchar](128) NULL,
[Col3] [nvarchar](128) NULL,
[Col4] [nvarchar](128) NULL,
[Col5] [nvarchar](128) NULL,
[Col6] [nvarchar](128) NULL,
[Col7] [nvarchar](128) NULL,
[Col8] [nvarchar](128) NULL,
[Col9] [nvarchar](128) NULL,
[Col10] [nvarchar](128) NULL,
[Col11] [nvarchar](128) NULL,
[Col12] [nvarchar](128) NULL,
[Col13] [nvarchar](128) NULL,
[Col14] [nvarchar](128) NULL,
[Col15] [nvarchar](128) NULL,
[Col16] [nvarchar](128) NULL
) ON [PRIMARY]
GO
--used to collect fragmentation levels
CREATE TABLE [dbo].[IndexOverview_FragLevels](
[ServerName] [nvarchar](128) NULL,
[DBName] [varchar](100) NOT NULL,
[Table] [sysname] NOT NULL,
[Index] [sysname] NULL,
[avg_fragmentation_in_percent] [float] NULL,
[page_count] [bigint] NULL
) ON [PRIMARY]
GO
--used to collect the most expensive and longest running queries
CREATE TABLE [dbo].[IndexOverview_LongestQueries](
[ServerName] [nvarchar](128) NULL,
[DBName] [varchar](100) NOT NULL,
[QueryName] [nvarchar](max) NULL,
[ExecutionCount] [bigint] NOT NULL,
[MaxElapsedTime] [bigint] NOT NULL,
[AvgElapsedTime] [bigint] NOT NULL,
[LogCreatedOn] [datetime] NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
--used to collect missing index information
CREATE TABLE [dbo].[IndexOverview_MissingIndex](
[ServerName] [nvarchar](128) NULL,
[DBName] [varchar](100) NOT NULL,
[DatabaseID] [smallint] NOT NULL,
[Avg_Estimated_Impact] [float] NULL,
[Last_User_Seek] [datetime] NULL,
[TableName] [nvarchar](128) NULL,
[Create_Statement] [nvarchar](4000) NULL
) ON [PRIMARY]
GO
--used to collect unused index information
CREATE TABLE [dbo].[IndexOverview_UnusedIndex](
[ServerName] [nvarchar](128) NULL,
[DBName] [varchar](100) NOT NULL,
[ObjectName] [sysname] NOT NULL,
[IndexName] [sysname] NULL,
[IndexID] [int] NOT NULL,
[UserSeek] [bigint] NOT NULL,
[UserScans] [bigint] NOT NULL,
[UserLookups] [bigint] NOT NULL,
[UserUpdates] [bigint] NOT NULL,
[TableRows] [bigint] NULL,
[drop statement] [nvarchar](790) NULL
) ON [PRIMARY]
go
步骤2:收集资料 ( Step 2: Collect the data )
This step will collect data into the tables created in step 1 above. The queries here use data from SQL Server Dynamic Management Views (DMV), so data is only current between server restarts. Because of this, it is a good idea to know when the last server restart was. In my case, restarts for database servers is usually my responsibility or I am in touch with the server guys to verify the last server reboot. This ensures that I have enough data in the DMV’s that my reports are useful. Make sure you change the variables marked with <> throughout the scripts prior to running. In addition to this, some of the queries contain a threshold for the amount of rows to bring back, so do not forget to modify this to your needs as well. Copy/Paste the scripts below to collect data.
此步骤将数据收集到以上步骤1中创建的表中。 此处的查询使用来自SQL Server动态管理视图(DMV)的数据,因此数据仅在服务器重新启动之间是最新的。 因此,最好知道上一次服务器重新启动的时间。 就我而言,通常由我负责数据库服务器的重新启动,或者与服务器人员联系以验证上次服务器重新启动。 这样可以确保我在DMV中有足够的数据以使我的报告有用。 在运行之前,请确保在整个脚本中更改标有<>的变量。 除此之外,某些查询还包含要带回的行数的阈值,因此请不要忘记根据您的需要对其进行修改。 复制/粘贴以下脚本以收集数据。
--get the data
--need to do this entire script for each db to be monitored
USE <add your database name to monitor> -- change this to dbname to collect from
Declare @DBName as varchar(100)
Set @DBName = '<add your database name to monitor>' --add db name here to be checked
--get Index reads with statement
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverviewReadsWithQuery_Stage]
([ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads]
,[Statement])
SELECT
@@ServerName,
DB_NAME(st.dbid) [Database],
OBJECT_NAME(st.objectid, st.dbid) [Object],
qs.execution_count,
qs.total_logical_reads,
qs.total_physical_reads,
st.text [Statement]
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverviewReadsWithQuery]
([ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads]
,[Statement])
SELECT [ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads]
,[Statement]
FROM [DBA_Monitoring].[dbo].[IndexOverviewReadsWithQuery_Stage]
WHERE [Database] = @DBName
--get Index reads with counts
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverviewReadsWithCounts_Stage]
([ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads])
SELECT @@ServerName, DB_NAME(database_id) [Database],
OBJECT_NAME(object_id, database_id) [Object],
execution_count,
total_logical_reads,
total_physical_reads
FROM sys.dm_exec_procedure_stats;
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverviewReadsWithCounts]
([ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads])
SELECT [ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads]
FROM [DBA_Monitoring].[dbo].[IndexOverviewReadsWithCounts_stage]
WHERE [Database] = @DBName
--get longest running queries
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_LongestQueries]
([ServerName]
,[DBName]
,[QueryName]
,[ExecutionCount]
,[MaxElapsedTime]
,[AvgElapsedTime]
,[LogCreatedOn])
(SELECT DISTINCT TOP 25 @@SERVERNAME as ServerName, @DBName as DBName,
t.TEXT QueryName,
s.execution_count AS ExecutionCount,
s.max_elapsed_time AS MaxElapsedTime,
ISNULL(s.total_elapsed_time / s.execution_count, 0) AS AvgElapsedTime,
s.creation_time AS LogCreatedOn
FROM sys.dm_exec_query_stats s
CROSS APPLY sys.dm_exec_sql_text( s.sql_handle ) t)
-- Unused Index Script
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_UnusedIndex]
([ServerName]
,[DBName]
,[ObjectName]
,[IndexName]
,[IndexID]
,[UserSeek]
,[UserScans]
,[UserLookups]
,[UserUpdates]
,[TableRows]
,[drop statement])
(SELECT TOP 25 @@SERVERNAME as ServerName, @DBName as DBName, o.name AS ObjectName
, i.name AS IndexName
, i.index_id AS IndexID
, dm_ius.user_seeks AS UserSeek
, dm_ius.user_scans AS UserScans
, dm_ius.user_lookups AS UserLookups
, dm_ius.user_updates AS UserUpdates
, p.TableRows
, 'DROP INDEX ' + QUOTENAME(i.name)
+ ' ON ' + QUOTENAME(s.name) + '.' + QUOTENAME(OBJECT_NAME(dm_ius.object_id)) as 'drop statement'
FROM sys.dm_db_index_usage_stats dm_ius
INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id AND dm_ius.object_id = i.object_id
INNER JOIN sys.objects o on dm_ius.object_id = o.object_id
INNER JOIN sys.schemas s on o.schema_id = s.schema_id
INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.object_id
FROM sys.partitions p GROUP BY p.index_id, p.object_id) p
ON p.index_id = dm_ius.index_id AND dm_ius.object_id = p.object_id
WHERE OBJECTPROPERTY(dm_ius.object_id,'IsUserTable') = 1
AND dm_ius.database_id = DB_ID()
AND i.type_desc = 'nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0)
-- Missing Index Script
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_MissingIndex]
([ServerName]
,[DBName]
,[DatabaseID]
,[Avg_Estimated_Impact]
,[Last_User_Seek]
,[TableName]
,[Create_Statement])
(SELECT TOP 25 @@SERVERNAME as ServerName, @DBName as DBName,
dm_mid.database_id AS DatabaseID,
dm_migs.avg_user_impact*(dm_migs.user_seeks+dm_migs.user_scans) Avg_Estimated_Impact,
dm_migs.last_user_seek AS Last_User_Seek,
object_name(dm_mid.object_id,dm_mid.database_id) AS [TableName],
'CREATE INDEX [IX_' + object_name(dm_mid.object_id,dm_mid.database_id) + '_'
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns,''),', ','_'),'[',''),']','') +
CASE
WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN '_'
ELSE ''
END
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.inequality_columns,''),', ','_'),'[',''),']','')
+ ']'
+ ' ON ' + dm_mid.statement
+ ' (' + ISNULL (dm_mid.equality_columns,'')
+ CASE WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN ',' ELSE
'' END
+ ISNULL (dm_mid.inequality_columns, '')
+ ')'
+ ISNULL (' INCLUDE (' + dm_mid.included_columns + ')', '') AS Create_Statement
FROM sys.dm_db_missing_index_groups dm_mig
INNER JOIN sys.dm_db_missing_index_group_stats dm_migs
ON dm_migs.group_handle = dm_mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details dm_mid
ON dm_mig.index_handle = dm_mid.index_handle
WHERE dm_mid.database_ID = DB_ID())
---fragmentation levels
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_FragLevels]
([ServerName]
,[DBName]
,[Table]
,[Index]
,[avg_fragmentation_in_percent]
,[page_count])
(SELECT @@SERVERNAME as ServerName, @DBName as DBName,
dbtables.[name] as 'Table',
dbindexes.[name] as 'Index',
indexstats.avg_fragmentation_in_percent,
indexstats.page_count
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats
INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id]
INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id]
INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id]
AND indexstats.index_id = dbindexes.index_id
WHERE indexstats.database_id = DB_ID())
GO
USE DBA_Monitoring --update to db name to run against
go
--Duplicate Index
WITH MyDuplicate AS (SELECT
Obj.[name] AS TableName,
Idx.[name] AS IndexName,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 1) AS Col1,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 2) AS Col2,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 3) AS Col3,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 4) AS Col4,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 5) AS Col5,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 6) AS Col6,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 7) AS Col7,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 8) AS Col8,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 9) AS Col9,
INDEX_Col(Sch.[name] + '.' + Obj.[name], Idx.index_id, 10) AS Col10
FROM sys.indexes Idx
INNER JOIN sys.objects Obj ON Idx.[object_id] = Obj.[object_id]
INNER JOIN sys.schemas Sch ON Sch.[schema_id] = Obj.[schema_id]
WHERE index_id > 0)
SELECT MD1.TableName, MD1.IndexName,
MD2.IndexName AS OverLappingIndex,
MD1.Col1, MD1.Col2, MD1.Col3, MD1.Col4,
MD1.Col5, MD1.Col6, MD1.Col7, MD1.Col8,
MD1.Col9, MD1.Col10
INTO ##Duplicates
FROM MyDuplicate MD1
INNER JOIN MyDuplicate MD2 ON MD1.tablename = MD2.tablename
AND MD1.indexname <> MD2.indexname
AND MD1.Col1 = MD2.Col1
AND (MD1.Col2 IS NULL OR MD2.Col2 IS NULL OR MD1.Col2 = MD2.Col2)
AND (MD1.Col3 IS NULL OR MD2.Col3 IS NULL OR MD1.Col3 = MD2.Col3)
AND (MD1.Col4 IS NULL OR MD2.Col4 IS NULL OR MD1.Col4 = MD2.Col4)
AND (MD1.Col5 IS NULL OR MD2.Col5 IS NULL OR MD1.Col5 = MD2.Col5)
AND (MD1.Col6 IS NULL OR MD2.Col6 IS NULL OR MD1.Col6 = MD2.Col6)
AND (MD1.Col7 IS NULL OR MD2.Col7 IS NULL OR MD1.Col7 = MD2.Col7)
AND (MD1.Col8 IS NULL OR MD2.Col8 IS NULL OR MD1.Col8 = MD2.Col8)
AND (MD1.Col9 IS NULL OR MD2.Col9 IS NULL OR MD1.Col9 = MD2.Col9)
AND (MD1.Col10 IS NULL OR MD2.Col10 IS NULL OR MD1.Col10 = MD2.Col10)
Declare @DBName as varchar(100)
Set @DBName = '<add your database name to be monitored>' --add db name here to be checked
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_DuplicateIndex]
(ServerName,
DBName,
[TableName]
,[IndexName]
,[OverLappingIndex]
,[Col1]
,[Col2]
,[Col3]
,[Col4]
,[Col5]
,[Col6]
,[Col7]
,[Col8]
,[Col9]
,[Col10]
)
(select @@ServerName, @DBName, [TableName]
,[IndexName]
,[OverLappingIndex]
,[Col1]
,[Col2]
,[Col3]
,[Col4]
,[Col5]
,[Col6]
,[Col7]
,[Col8]
,[Col9]
,[Col10]
from ##Duplicates)
GO
--get Index reads with counts
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverviewReadsWithCounts_Stage]
([ServerName]
,[Database]
,[Object]
,[execution_count]
,[total_logical_reads]
,[total_physical_reads])
SELECT @@ServerName, DB_NAME(database_id) [Database],
OBJECT_NAME(object_id, database_id) [Object],
execution_count,
total_logical_reads,
total_physical_reads
FROM sys.dm_exec_procedure_stats;
GO
步骤3:产生报告 ( Step 3: Generate the reports )
Each report is generated with HTML and sent via dbMail. These reports are called from within the SQL Agent Job one at a time after all of the data has been collected. Copy / Paste and run the scripts below to see the reports. Make sure to change the variables marked <> throughout.
每个报告都是使用HTML生成的,并通过dbMail发送。 收集完所有数据后,一次从SQL Agent Job中调用这些报告。 复制/粘贴并运行以下脚本以查看报告。 确保始终更改标记为<>的变量。
报告:未使用的索引 ( Report: Unused Index )
This report will provide unused indexes that are currently setup. DMV data is greatly important here as all indexes are not activated all the time. So be careful of your last server restart when reviewing this report. This report will also provide the appropriate drop statement that you can run to remove the unused index following analysis. I highly recommend scripting and saving the indexes prior to removing them in the event you need to add them back later.
该报告将提供当前设置的未使用索引。 DMV数据在这里非常重要,因为并非一直都激活所有索引。 因此,在查看此报告时,请注意您上次服务器重启。 该报告还将提供适当的drop语句,您可以运行该语句以在分析后删除未使用的索引。 我强烈建议编写脚本并保存索引,然后再删除它们,以防您以后需要将它们重新添加。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Unused Index ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_UnusedIndex
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Object Name</th>' +
N'<th>Index Name</th>' +
N'<th>User Seek</th>' +
N'<th>User Scan</th>' +
N'<th>User Lookups</th>' +
N'<th>User Updates</th>' +
N'<th>Table Rows</th>' +
N'<th>Drop Statement</th>' +
N'</tr>' +
CAST ( ( SELECT td=[ServerName],''
,td=[DBname],''
,td=[ObjectName],''
,td=[IndexName],''
,td=[USERSeek],''
,td=[UserScans],''
,td=[UserLookups],''
,td=[UserUpdates],''
,td=[TableRows],''
,td=[drop statement],''
FROM DBA_Monitoring.dbo.IndexOverview_UnusedIndex
order by ServerName, DBName, IndexName
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| DROP INDEX [IX_Samples] ON [dbo].[databaseName] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| DROP INDEX [IX_ Samples ] ON [dbo]。[ databaseName ] |
报告:缺少索引 ( Report: Missing Index )
This report will contain the missing indexes along with the create statement that could be used following analysis to generate the missing index. DMV data is greatly important here as all indexes are not activated all the time. So be careful of your last server restart when reviewing this report.
该报告将包含缺少的索引以及创建语句,可在分析之后使用该语句生成丢失的索引。 DMV数据在这里非常重要,因为并非一直都激活所有索引。 因此,在查看此报告时,请注意您上次服务器重启。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Missing Index ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_MissingIndex
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Avg Estimated Impact (milliseconds)</th>' +
N'<th>Last User Seek</th>' +
N'<th>Table Name</th>' +
N'<th>Create Statement</th>' +
N'</tr>' +
CAST ( ( SELECT td=[ServerName],''
,td=[DBname],''
,td=Cast([Avg_Estimated_Impact] as varchar(25)),''
,td=[Last_User_Seek],''
,td=[TableName],''
,td=[Create_Statement],''
FROM DBA_Monitoring.dbo.IndexOverview_MissingIndex
order by ServerName, DBName, TableName
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
报告:重复索引 ( Report: Duplicate Index )
This report contains duplicate or overlapping indexes and a drop statement to clean them up following analysis. I highly recommend scripting and saving the indexes prior to removing them in the event you need to add them back later.
该报告包含重复或重叠的索引以及一个drop语句,以在分析后清理它们。 我强烈建议编写脚本并保存索引,然后再删除它们,以防您以后需要将它们重新添加。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Duplicate Index ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_DuplicateIndex
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Table Name</th>' +
N'<th>Over Lapping Index</th>' +
N'<th>Col1</th>' +
N'<th>Col2</th>' +
N'<th>Col3</th>' +
N'<th>Col4</th>' +
N'<th>Col5</th>' +
N'<th>Col6</th>' +
N'<th>Col7</th>' +
N'<th>Col8</th>' +
N'<th>Col9</th>' +
N'<th>Col10</th>' +
N'</tr>' +
CAST ( ( SELECT td=[servername],''
,td=[dbname],''
,td=[OverlappingIndex],''
,td=[Col1],''
,td=[Col2],''
,td=[Col3],''
,td=[Col4],''
,td=[Col5],''
,td=[Col6],''
,td=[Col7],''
,td=[Col8],''
,td=[Col9],''
,td=[Col10],''
FROM DBA_Monitoring.dbo.IndexOverview_DuplicateIndex
ORDER BY
ServerName, DBName, TableName,IndexName
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
The example below has been truncated to only show up to 3 columns however the report will return up to 10 index columns.
下面的示例已被截断为仅显示最多3列,但是报告将最多返回10个索引列。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
报告:最长的运行查询 ( Report: Longest Running Queries )
This report will contain the longest running/most expensive queries. This report is useful to see how many times a query is executed along with how long it takes to run.
该报告将包含运行时间最长/最昂贵的查询。 该报告对于查看查询执行了多少次以及运行需要多长时间很有用。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Longest Running Queries ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_LongestQueries
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Query</th>' +
N'<th>Execution Count</th>' +
N'<th>Max Elasped Time (ms)</th>' +
N'<th>Avg Elsaped Time (ms)</th>' +
N'</tr>' +
CAST ( ( SELECT td=[ServerName],''
,td=[DBname],''
,td=[Queryname],''
,td=[ExecutionCount],''
,td=MaxElapsedTime,''
,td=AvgElapsedTime,''
FROM DBA_Monitoring.dbo.IndexOverview_LongestQueries
order by ServerName, DBName, QueryName, ExecutionCount
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
报告:带语句的索引读取 ( Report: Index Reads with Statement )
This report will contain statistics against each object executed including the query that ran. In addition to this you will also see total logical reads and total physical reads. These are great for determining if queries are being run in memory or disk. These statistics are useful to determine if adding additional indexes could assist the query to run faster. Another benefit could be in determinig hardware updates such as memory or even server level settings such as the “maximum server memory” setting.
该报告将包含针对每个执行的对象的统计信息,包括运行的查询。 除此之外,您还将看到逻辑读取总数和物理读取总数。 这些对于确定查询是在内存还是磁盘中运行非常有用。 这些统计信息对于确定添加其他索引是否可以帮助查询更快地运行非常有用。 另一个好处是可以确定硬件更新(例如内存),甚至可以确定服务器级别的设置(例如“最大服务器内存”设置)。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Index Reads with Statements ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverviewReadsWithQuery
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Object</th>' +
N'<th>Execution Count</th>' +
N'<th>Total Logical Reads</th>' +
N'<th>Total Physical Reads</th>' +
N'<th>Statement</th>' +
N'</tr>' +
CAST ( ( SELECT top 100 td=[ServerName],''
,td=[DataBase],''
,td=[Object],''
,td=[execution_count],''
,td=total_logical_reads,''
,td=total_physical_reads,''
,td=[Statement],''
FROM DBA_Monitoring.dbo.IndexOverviewReadsWithQuery
order by ServerName, [Database], [Object]
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
报告:带有计数的索引读取 ( Report: Index Reads with Counts )
This report will contain counts for each database object executed against. This is similar to the Index Reads with Statement report however this report shows just the counts. This is great for just a quick peak into what is going on.
该报告将包含针对每个数据库对象执行的计数。 这类似于“带有语句的索引读取”报告,但是此报告仅显示计数。 这对于快速了解正在发生的事情非常有用。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Index Reads with Counts ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverviewReadsWithCounts
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Object</th>' +
N'<th>Execution Count</th>' +
N'<th>Total Logical Reads</th>' +
N'<th>Total Physical Reads</th>' +
N'</tr>' +
CAST ( ( SELECT td=[ServerName],''
,td=[DataBase],''
,td=[Object],''
,td=[execution_count],''
,td=total_logical_reads,''
,td=total_physical_reads,''
FROM DBA_Monitoring.dbo.IndexOverviewReadsWithCounts
order by ServerName, [Database], [Object]
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
This report will contain counts for each database object executed.
该报告将包含每个执行的数据库对象的计数。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
报告:碎片级别 ( Report: Fragmentation Levels )
This report will contains index fragmentation % and page count for each index. When looking at the fragmentation %, a good rule of thumb for managing indexes is:
该报告将包含每个索引的索引碎片百分比和页数。 查看碎片%时,管理索引的一个好的经验法则是:
- 0-5% fragmentation leave alone 0-5%的碎片留下
- 6-30% reorganize the index 6-30%重组指数
- 31% or higher rebuild the index 31%或更高的重建指数
Also, do not forget to consider page count. Low page count and high fragmentation may not be an issue. For example 80% fragmentation and 5 pages should not cause alarm.
另外,不要忘记考虑页数。 低页数和高碎片可能不是问题。 例如,80%的碎片和5页不应引起警报。
SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject='SQL Server - Index Overview_Fragmentation Levels ''<Add Server/Database Names>'''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_FragLevels
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N'<table border="1">' +
N'<tr>' +
N'<th>Server Name</th>' +
N'<th>DB Name</th>' +
N'<th>Table</th>' +
N'<th>Index</th>' +
N'<th>Fragmentation %</th>' +
N'<th>Page Count</th>' +
N'</tr>' +
CAST ( ( SELECT td=[ServerName],''
,td=[DBname],''
,td=[Table],''
,td=IsNull([Index], 'HEAP - no clustered index'),''
,td=convert(varchar, round([avg_fragmentation_in_percent], 2)),''
,td=[page_count],''
FROM DBA_Monitoring.dbo.IndexOverview_FragLevels
order by ServerName, DBName, [Table], [Index]
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = '<add your mail profile here>',
@recipients = '<add your email here>',
@subject = @Subject,
@body = @tableHTML,
@body_format = 'HTML' ;
END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
步骤4:清除资料 ( Step 4: Purge Data )
Following the data collection you will need to clean up the temp and staging tables before your next run. Paste this script to clean up the temp and staging tables. In the SQL Agent Job, I have this as the first step therefore the previous data is available between runs. If running it manually outside the SQL Agent job, you can just run this after you run reports. Copy / Paste the script below to purge the data prior to the next run.
在收集数据之后,您将需要在下一次运行之前清理临时表和登台表。 粘贴此脚本以清理临时表和登台表。 在SQL Agent Job中,我将其作为第一步,因此在运行之间可以使用先前的数据。 如果在SQL Agent作业之外手动运行它,则可以在运行报表后运行它。 复制/粘贴下面的脚本,以在下次运行之前清除数据。
--clean up
USE DBA_Monitoring
go
Truncate Table dbo.IndexOverviewReadsWithCounts
go
Truncate Table dbo.IndexOverviewReadsWithQuery
go
Truncate Table dbo.IndexOverviewReadsWithCounts_Stage
go
Truncate Table dbo.IndexOverviewReadsWithQuery_Stage
Truncate Table dbo.IndexOverview_DuplicateIndex
go
Truncate Table dbo.IndexOverview_FragLevels
go
Truncate Table dbo.IndexOverview_LongestQueries
go
Truncate Table dbo.IndexOverview_MissingIndex
go
Truncate Table dbo.IndexOverview_UnusedIndex
go
IF OBJECT_ID('tempdb..##Duplicates') IS NOT NULL
/*Then it exists*/
DROP TABLE ##Duplicates
步骤5:SQL代理作业 ( Step 5: SQL Agent Job )
The SQL Agent Job ties the entire process together by calling steps 2-4 above. I usually call this job ad-hoc when someone complains about performance, however you could schedule it as well. Again, just a warning the data is only valid between server restarts so if you restarted the server a day or two ago your data will not be as rich as data collected after a month between the last server restart, so be wary of restarts. Below is what the SQL Agent Job looks like:
SQL代理作业通过调用上面的步骤2-4将整个过程联系在一起。 当有人抱怨绩效时,我通常称其为临时工,但是您也可以安排它。 再次提醒您,该数据仅在服务器重新启动之间才有效,因此,如果您在一两天前重新启动服务器,则数据将不如上次服务器重新启动之间一个月后收集的数据那样丰富,因此请小心重启。 下面是SQL Agent Job的外观:
Copy/Paste/Run the script below to create the above SQL Agent Job. Again please change the variables marked with <>
复制/粘贴/运行以下脚本以创建上述SQL代理作业。 再次请更改标有<>的变量
USE [msdb]
GO
/****** Object: Job [Index Overview] ******/
BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0
/****** Object: JobCategory [[Uncategorized (Local)]]] ******/
IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
BEGIN
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
END
DECLARE @jobId BINARY(16)
EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'Index Overview',
@enabled=1,
@notify_level_eventlog=0,
@notify_level_email=0,
@notify_level_netsend=0,
@notify_level_page=0,
@delete_level=0,
@description=N'Run this job (change run in db at script level) for a database that you want to get an overview of index''s
This includes the following data pulls and subsequent reports:
Duplicate Index
Missing Index
Unused Index
Longest Queries
Fragmentation Levels
Index Read Counts
Index Read Statements',
@category_name=N'[Uncategorized (Local)]',
@owner_login_name=N'SA', @job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [clean up] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'clean up',
@step_id=1,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'--clean up
USE DBA_Monitoring
go
Truncate Table dbo.IndexOverviewReadsWithCounts
go
Truncate Table dbo.IndexOverviewReadsWithQuery
go
Truncate Table dbo.IndexOverviewReadsWithCounts_Stage
go
Truncate Table dbo.IndexOverviewReadsWithQuery_Stage
go
Truncate Table dbo.IndexOverview_DuplicateIndex
go
Truncate Table dbo.IndexOverview_FragLevels
go
Truncate Table dbo.IndexOverview_LongestQueries
go
Truncate Table dbo.IndexOverview_MissingIndex
go
Truncate Table dbo.IndexOverview_UnusedIndex
go
IF OBJECT_ID(''tempdb..##Duplicates'') IS NOT NULL
/*Then it exists*/
DROP TABLE ##Duplicates
',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=6
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [get the data] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'get the data',
@step_id=2,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'--get the data
--need to do this entire script for each db
-- change DB name above to collect from
Declare @DBName as varchar(100)
Set @DBName = ''<Add your db name to be monitored here>'' --add db name here to be checked
--get longest running queries
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_LongestQueries]
([ServerName]
,[DBName]
,[QueryName]
,[ExecutionCount]
,[MaxElapsedTime]
,[AvgElapsedTime]
,[LogCreatedOn])
(SELECT DISTINCT TOP 25 @@SERVERNAME as ServerName, @DBName as DBName,
t.TEXT QueryName,
s.execution_count AS ExecutionCount,
s.max_elapsed_time AS MaxElapsedTime,
ISNULL(s.total_elapsed_time / s.execution_count, 0) AS AvgElapsedTime,
s.creation_time AS LogCreatedOn
FROM sys.dm_exec_query_stats s
CROSS APPLY sys.dm_exec_sql_text( s.sql_handle ) t)
-- Unused Index Script
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_UnusedIndex]
([ServerName]
,[DBName]
,[ObjectName]
,[IndexName]
,[IndexID]
,[UserSeek]
,[UserScans]
,[UserLookups]
,[UserUpdates]
,[TableRows]
,[drop statement])
(SELECT TOP 25 @@SERVERNAME as ServerName, @DBName as DBName, o.name AS ObjectName
, i.name AS IndexName
, i.index_id AS IndexID
, dm_ius.user_seeks AS UserSeek
, dm_ius.user_scans AS UserScans
, dm_ius.user_lookups AS UserLookups
, dm_ius.user_updates AS UserUpdates
, p.TableRows
, ''DROP INDEX '' + QUOTENAME(i.name)
+ '' ON '' + QUOTENAME(s.name) + ''.'' + QUOTENAME(OBJECT_NAME(dm_ius.object_id)) as ''drop statement''
FROM sys.dm_db_index_usage_stats dm_ius
INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id AND dm_ius.object_id = i.object_id
INNER JOIN sys.objects o on dm_ius.object_id = o.object_id
INNER JOIN sys.schemas s on o.schema_id = s.schema_id
INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.object_id
FROM sys.partitions p GROUP BY p.index_id, p.object_id) p
ON p.index_id = dm_ius.index_id AND dm_ius.object_id = p.object_id
WHERE OBJECTPROPERTY(dm_ius.object_id,''IsUserTable'') = 1
AND dm_ius.database_id = DB_ID()
AND i.type_desc = ''nonclustered''
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0)
-- Missing Index Script
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_MissingIndex]
([ServerName]
,[DBName]
,[DatabaseID]
,[Avg_Estimated_Impact]
,[Last_User_Seek]
,[TableName]
,[Create_Statement])
(SELECT TOP 25 @@SERVERNAME as ServerName, @DBName as DBName,
dm_mid.database_id AS DatabaseID,
dm_migs.avg_user_impact*(dm_migs.user_seeks+dm_migs.user_scans) Avg_Estimated_Impact,
dm_migs.last_user_seek AS Last_User_Seek,
object_name(dm_mid.object_id,dm_mid.database_id) AS [TableName],
''CREATE INDEX [IX_'' + object_name(dm_mid.object_id,dm_mid.database_id) + ''_''
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns,''''),'', '',''_''),''['',''''),'']'','''') +
CASE
WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN ''_''
ELSE ''''
END
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.inequality_columns,''''),'', '',''_''),''['',''''),'']'','''')
+ '']''
+ '' ON '' + dm_mid.statement
+ '' ('' + ISNULL (dm_mid.equality_columns,'''')
+ CASE WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL THEN '','' ELSE
'''' END
+ ISNULL (dm_mid.inequality_columns, '''')
+ '')''
+ ISNULL ('' INCLUDE ('' + dm_mid.included_columns + '')'', '''') AS Create_Statement
FROM sys.dm_db_missing_index_groups dm_mig
INNER JOIN sys.dm_db_missing_index_group_stats dm_migs
ON dm_migs.group_handle = dm_mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details dm_mid
ON dm_mig.index_handle = dm_mid.index_handle
WHERE dm_mid.database_ID = DB_ID())
---fragmentation levels
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_FragLevels]
([ServerName]
,[DBName]
,[Table]
,[Index]
,[avg_fragmentation_in_percent]
,[page_count])
(SELECT @@SERVERNAME as ServerName, @DBName as DBName,
dbtables.[name] as ''Table'',
dbindexes.[name] as ''Index'',
indexstats.avg_fragmentation_in_percent,
indexstats.page_count
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS indexstats
INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id]
INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id]
INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id]
AND indexstats.index_id = dbindexes.index_id
WHERE indexstats.database_id = DB_ID())
GO
--USE DBA_Monitoring --update to db name to run against
--go
--Duplicate Index
WITH MyDuplicate AS (SELECT
Obj.[name] AS TableName,
Idx.[name] AS IndexName,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 1) AS Col1,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 2) AS Col2,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 3) AS Col3,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 4) AS Col4,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 5) AS Col5,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 6) AS Col6,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 7) AS Col7,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 8) AS Col8,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 9) AS Col9,
INDEX_Col(Sch.[name] + ''.'' + Obj.[name], Idx.index_id, 10) AS Col10,
FROM sys.indexes Idx
INNER JOIN sys.objects Obj ON Idx.[object_id] = Obj.[object_id]
INNER JOIN sys.schemas Sch ON Sch.[schema_id] = Obj.[schema_id]
WHERE index_id > 0)
SELECT MD1.TableName, MD1.IndexName,
MD2.IndexName AS OverLappingIndex,
MD1.Col1, MD1.Col2, MD1.Col3, MD1.Col4,
MD1.Col5, MD1.Col6, MD1.Col7, MD1.Col8,
MD1.Col9, MD1.Col10
INTO ##Duplicates
FROM MyDuplicate MD1
INNER JOIN MyDuplicate MD2 ON MD1.tablename = MD2.tablename
AND MD1.indexname <> MD2.indexname
AND MD1.Col1 = MD2.Col1
AND (MD1.Col2 IS NULL OR MD2.Col2 IS NULL OR MD1.Col2 = MD2.Col2)
AND (MD1.Col3 IS NULL OR MD2.Col3 IS NULL OR MD1.Col3 = MD2.Col3)
AND (MD1.Col4 IS NULL OR MD2.Col4 IS NULL OR MD1.Col4 = MD2.Col4)
AND (MD1.Col5 IS NULL OR MD2.Col5 IS NULL OR MD1.Col5 = MD2.Col5)
AND (MD1.Col6 IS NULL OR MD2.Col6 IS NULL OR MD1.Col6 = MD2.Col6)
AND (MD1.Col7 IS NULL OR MD2.Col7 IS NULL OR MD1.Col7 = MD2.Col7)
AND (MD1.Col8 IS NULL OR MD2.Col8 IS NULL OR MD1.Col8 = MD2.Col8)
AND (MD1.Col9 IS NULL OR MD2.Col9 IS NULL OR MD1.Col9 = MD2.Col9)
AND (MD1.Col10 IS NULL OR MD2.Col10 IS NULL OR MD1.Col10 = MD2.Col10)
Declare @DBName as varchar(100)
Set @DBName = ''<add your database name here to monitor>'' --add db name here to be checked
INSERT INTO [DBA_Monitoring].[dbo].[IndexOverview_DuplicateIndex]
(ServerName,
DBName,
[TableName]
,[IndexName]
,[OverLappingIndex]
,[Col1]
,[Col2]
,[Col3]
,[Col4]
,[Col5]
,[Col6]
,[Col7]
,[Col8]
,[Col9]
,[Col10]
)
(select @@ServerName, @DBName, [TableName]
,[IndexName]
,[OverLappingIndex]
,[Col1]
,[Col2]
,[Col3]
,[Col4]
,[Col5]
,[Col6]
,[Col7]
,[Col8]
,[Col9]
,[Col10]
from ##Duplicates)',
@database_name=N'<add your database name to be monitored here>',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=2
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report - duplicate Index] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report - duplicate Index',
@step_id=3,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Duplicate Index ''''Add Server Name''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_DuplicateIndex
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Table Name</th>'' +
N''<th>Over Lapping Index</th>'' +
N''<th>Col1</th>'' +
N''<th>Col2</th>'' +
N''<th>Col3</th>'' +
N''<th>Col4</th>'' +
N''<th>Col5</th>'' +
N''<th>Col6</th>'' +
N''<th>Col7</th>'' +
N''<th>Col8</th>'' +
N''<th>Col9</th>'' +
N''<th>Col10</th>'' +
N''</tr>'' +
CAST ( ( SELECT td=[servername],''''
,td=[dbname],''''
,td=[OverlappingIndex],''''
,td=[Col1],''''
,td=[Col2],''''
,td=[Col3],''''
,td=[Col4],''''
,td=[Col5],''''
,td=[Col6],''''
,td=[Col7],''''
,td=[Col8],''''
,td=[Col9],''''
,td=[Col10],''''
FROM DBA_Monitoring.dbo.IndexOverview_DuplicateIndex
ORDER BY
ServerName, DBName, TableName,IndexName
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile>'',
@recipients = ''<add your email>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report - missing index] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report - missing index',
@step_id=4,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Missing Index ''''Add Server Name''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_MissingIndex
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Avg Estimated Impact (milliseconds)</th>'' +
N''<th>Last User Seek</th>'' +
N''<th>Table Name</th>'' +
N''<th>Create Statement</th>'' +
N''</tr>'' +
CAST ( ( SELECT td=[ServerName],''''
,td=[DBname],''''
,td=Cast([Avg_Estimated_Impact] as varchar(25)),''''
,td=[Last_User_Seek],''''
,td=[TableName],''''
,td=[Create_Statement],''''
FROM DBA_Monitoring.dbo.IndexOverview_MissingIndex
order by ServerName, DBName, TableName
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile>'',
@recipients = ''<add your email>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=2
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report- unused index] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report- unused index',
@step_id=5,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Unused Index ''''Add Server Name''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_UnusedIndex
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Object Name</th>'' +
N''<th>Index Name</th>'' +
N''<th>User Seek</th>'' +
N''<th>User Scan</th>'' +
N''<th>User Lookups</th>'' +
N''<th>User Updates</th>'' +
N''<th>Table Rows</th>'' +
N''<th>Drop Statement</th>'' +
N''</tr>'' +
CAST ( ( SELECT td=[ServerName],''''
,td=[DBname],''''
,td=[ObjectName],''''
,td=[IndexName],''''
,td=[USERSeek],''''
,td=[UserScans],''''
,td=[UserLookups],''''
,td=[UserUpdates],''''
,td=[TableRows],''''
,td=[drop statement],''''
FROM DBA_Monitoring.dbo.IndexOverview_UnusedIndex
order by ServerName, DBName, IndexName
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile>'',
@recipients = ''<add your email>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=2
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report - longest running queries] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report - longest running queries',
@step_id=6,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Longest Running Queries ''''Add Server Name''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_LongestQueries
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Query Name</th>'' +
N''<th>Execution Count</th>'' +
N''<th>Max Elasped Time (milliseconds)</th>'' +
N''<th>Avg Elsaped Time (milliseconds)</th>'' +
N''</tr>'' +
CAST ( ( SELECT td=[ServerName],''''
,td=[DBname],''''
,td=[Queryname],''''
,td=[ExecutionCount],''''
,td=MaxElapsedTime,''''
,td=AvgElapsedTime,''''
FROM DBA_Monitoring.dbo.IndexOverview_LongestQueries
order by ServerName, DBName, QueryName, ExecutionCount
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile>'',
@recipients = ''<add your email>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report - index read counts] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report - index read counts',
@step_id=7,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Index Reads with Counts ''''<Add Server/Database Names>''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverviewReadsWithCounts
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Object</th>'' +
N''<th>Execution Count</th>'' +
N''<th>Total Logical Reads</th>'' +
N''<th>Total Physical Reads</th>'' +
N''</tr>'' +
CAST ( ( SELECT td=[ServerName],''''
,td=[DataBase],''''
,td=[Object],''''
,td=[execution_count],''''
,td=total_logical_reads,''''
,td=total_physical_reads,''''
FROM DBA_Monitoring.dbo.IndexOverviewReadsWithCounts
order by ServerName, [Database], [Object]
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile here>'',
@recipients = ''<add your email here>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=2
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report - index read statements] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report - index read statements',
@step_id=8,
@cmdexec_success_code=0,
@on_success_action=3,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Index Reads with Statements ''''<Add Server/Database Names>''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverviewReadsWithQuery
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Object</th>'' +
N''<th>Execution Count</th>'' +
N''<th>Total Logical Reads</th>'' +
N''<th>Total Physical Reads</th>'' +
N''<th>Statement</th>'' +
N''</tr>'' +
CAST ( ( SELECT top 100 td=[ServerName],''''
,td=[DataBase],''''
,td=[Object],''''
,td=[execution_count],''''
,td=total_logical_reads,''''
,td=total_physical_reads,''''
,td=[Statement],''''
FROM DBA_Monitoring.dbo.IndexOverviewReadsWithQuery
order by ServerName, [Database], [Object]
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile here>'',
@recipients = ''<add your email here>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=2
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object: Step [report - fragmentation levels] ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'report - fragmentation levels',
@step_id=9,
@cmdexec_success_code=0,
@on_success_action=1,
@on_success_step_id=0,
@on_fail_action=2,
@on_fail_step_id=0,
@retry_attempts=0,
@retry_interval=0,
@os_run_priority=0, @subsystem=N'TSQL',
@command=N'SET nocount ON
--
DECLARE @Subject VARCHAR (100)
SET @Subject=''SQL Server - Index Overview_Fragmentation Levels ''''Add Server Name''''''
DECLARE @Count AS INT
SELECT @Count=COUNT(*) FROM DBA_Monitoring.dbo.IndexOverview_FragLevels
PRINT @Count
IF @Count > 0
BEGIN
DECLARE @tableHTML NVARCHAR(MAX) ;
SET @tableHTML =
N''<table border="1">'' +
N''<tr>'' +
N''<th>Server Name</th>'' +
N''<th>DB Name</th>'' +
N''<th>Table</th>'' +
N''<th>Index</th>'' +
N''<th>Fragmentation %</th>'' +
N''<th>Page Count</th>'' +
N''</tr>'' +
CAST ( ( SELECT td=[ServerName],''''
,td=[DBname],''''
,td=[Table],''''
,td=IsNull([Index], ''HEAP - no clustered index''),''''
,td=convert(varchar, round([avg_fragmentation_in_percent], 2)),''''
,td=[page_count],''''
FROM DBA_Monitoring.dbo.IndexOverview_FragLevels
order by ServerName, DBName, [Table], [Index]
FOR XML PATH(''tr''), TYPE
) AS NVARCHAR(MAX) ) +
N''</table>'' ;
EXEC msdb.dbo.sp_send_dbmail
@profile_name = ''<add your mail profile>'',
@recipients = ''<add your email>'',
@subject = @Subject,
@body = @tableHTML,
@body_format = ''HTML'' ;
END',
@database_name=N'DBA_Monitoring',
@output_file_name=N'<add your path>\IndexOverviewErrors.txt',
@flags=2
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:
GO
摘要: ( Summary: )
This is a step by step method to collect a high level overview of the condition of the Index’s and query executions for a specific database on your server. This information is taken directly from SQL Server making it readily available at any time as long as there has been a reasonable amount of time between server restarts. Data collected may help you answer the ever reoccurring questions about database performance and what you can do to improve it.
这是一种分步方法,用于收集有关索引状况的高级概述,并查询服务器上特定数据库的执行情况。 只要在服务器重新启动之间有一段合理的时间,此信息就直接从SQL Server获取,因此随时可以使用。 收集的数据可以帮助您回答有关数据库性能以及如何做的改进问题。
Included in this package are the following:
此程序包中包含以下内容:
- Tables:表格:
- IndexOverviewReadsWithCounts_stage IndexOverviewReadsWithCounts_stage
- IndexOverviewReadsWithQuery_stage IndexOverviewReadsWithQuery_stage
- IndexOverviewReadsWithCounts IndexOverviewReadsWithCounts
- IndexOverviewReadsWithQuery IndexOverviewReadsWithQuery
- IndexOverview_DuplicateIndex IndexOverview_DuplicateIndex
- IndexOverview_FragLevels IndexOverview_FragLevels
- IndexOverview_LongestQueries IndexOverview_Longest查询
- IndexOverview_MissingIndex IndexOverview_MissingIndex
- IndexOverview_UnusedIndex IndexOverview_UnusedIndex
- Reports报告书
- Duplicate Index 重复索引
- Missing Index 缺少索引
- Unused Index 未使用索引
- Longest Queries 最长查询
- Fragmentation Levels 碎片级别
- Index Read Counts 索引读取计数
- Index Read Statements 索引读取语句
- SQL Agent JobSQL代理作业
- Index Overview 索引总览
翻译自: https://www.sqlshack.com/what-is-causing-database-slowdowns/
数据库数据变大会导致查询慢