Next, you will look for missing index
warnings in the cached execution plans for stored procedures in this
database, using the query shown in Listing 47.
LISTING 47: Missing index warnings for cached plans
-- Find missing index warnings for cached plans in the current database
-- Note: This query could take some time on a busy instance
SELECT TOP(25) OBJECT_NAME(objectid) AS [ObjectName],query_plan,
cp.objtype, cp.usecounts
FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK)
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
WHERE CAST(query_plan AS NVARCHAR(MAX)) LIKE N'%MissingIndex%'
AND dbid= DB_ID()
ORDER BY cp.usecounts DESC OPTION (RECOMPILE);
-- Helps you connect missing indexes to specific stored procedures or queries
-- This can help you decide whether to add them or not
This query returns information about cached
execution plans that have “missing index” warnings. It will give you
the stored procedure name, the query plan, and the use count for that
cache execution plan. This can help you decide whether a particular
“missing index” is really important or not. You should use this query
along with the query shown in Listing 46 to help determine whether you should add any new indexes to a particular table.
Next, using the query shown in Listing 48, you can find out which tables and indexes are using the most space in the SQL Server buffer pool.
LISTING 48: Buffer usage by table and index
-- Breaks down buffers used by current database
-- by object (table, index) in the buffer cache
SELECT OBJECT_NAME(p.[object_id]) AS [ObjectName],
p.index_id, COUNT(*)/128 AS [Buffer size(MB)], COUNT(*) AS [BufferCount],
p.data_compression_desc AS [CompressionType]
FROM sys.allocation_units AS a WITH (NOLOCK)
INNER JOIN sys.dm_os_buffer_descriptors AS b WITH (NOLOCK)
ON a.allocation_unit_id= b.allocation_unit_id
INNER JOIN sys.partitions AS p WITH (NOLOCK)
ON a.container_id= p.hobt_id
WHERE b.database_id= CONVERT(int,DB_ID())
AND p.[object_id] > 100
GROUP BY p.[object_id], p.index_id, p.data_compression_desc
ORDER BY [BufferCount] DESC OPTION (RECOMPILE);
-- Tells you what tables and indexes are
-- using the most memory in the buffer cache
This query indicates which indexes and tables in
the current database are using the most memory in the SQL Server buffer
pool. It also shows you whether the index is using any form of data
compression. If you see an index that is using a large amount of space
in the buffer pool, you should investigate whether that index might be
a good candidate for SQL Server data compression, assuming that you
have SQL Server 2008 or later Enterprise Edition.
An ideal data compression candidate would be a
large, static table that has highly compressible data. In such a case,
you might see as much as a 10:1 compression ratio, meaning the
compressed index would take up far less space in the buffer pool, and
in the data file on disk. In my experience, I have typically seen
anywhere from 2:1 up to 4:1 for average compression ratios. A poor data
compression candidate would be a smaller, highly volatile table
containing data that does not compress very well. In that case, you
would most likely be better off without using data compression.
Next, you will find out the size (in terms of row
counts) and the data compression status of all the tables in this
database, using the query shown in Listing 49.
LISTING 49: Table names, row counts, and compression status
-- Get Table names, row counts, and compression status
-- for the clustered index or heap
SELECT OBJECT_NAME(object_id) AS [ObjectName],
SUM(Rows) AS [RowCount], data_compression_desc AS [CompressionType]
FROM sys.partitions WITH (NOLOCK)
WHERE index_id < 2 --ignore the partitions from the non-clustered index if any
AND OBJECT_NAME(object_id) NOT LIKE N'sys%'
AND OBJECT_NAME(object_id) NOT LIKE N'queue_%'
AND OBJECT_NAME(object_id) NOT LIKE N'filestream_tombstone%'
AND OBJECT_NAME(object_id) NOT LIKE N'fulltext%'
AND OBJECT_NAME(object_id) NOT LIKE N'ifts_comp_fragment%'
AND OBJECT_NAME(object_id) NOT LIKE N'filetable_updates%'
GROUP BY object_id, data_compression_desc
ORDER BY SUM(Rows) DESC OPTION (RECOMPILE);
-- Gives you an idea of table sizes, and possible data compression opportunities
This query returns all your table sizes,
including row count and data compression status (for the clustered
index), ordered by row counts. It is a good idea to have a notion of
how many millions or billions of rows are contained in the larger
tables in your database. This is one indirect way of keeping tabs on
the growth and activity of your database. Knowing the compression
status of the clustered index of your largest tables is also very
useful, as it might uncover some good candidates for data compression.
As previously discussed, SQL Server data compression can be a huge win
in many scenarios if you are able to take advantage of it with
Enterprise Edition.
Next, using the query shown in Listing 50, you can find out the last time that statistics were updated for all indexes in the database.
LISTING 50: Last statistics update for all indexes
-- When were Statistics last updated on all indexes?
SELECT o.name, i.name AS [Index Name],STATS_DATE(i.[object_id],
i.index_id) AS [Statistics Date], s.auto_created,
s.no_recompute, s.user_created, st.row_count
FROM sys.objects AS o WITH (NOLOCK)
INNER JOIN sys.indexes AS i WITH (NOLOCK)
ON o.[object_id] = i.[object_id]
INNER JOIN sys.stats AS s WITH (NOLOCK)
ON i.[object_id] = s.[object_id]
AND i.index_id= s.stats_id
INNER JOIN sys.dm_db_partition_stats AS st WITH (NOLOCK)
ON o.[object_id] = st.[object_id]
AND i.[index_id] = st.[index_id]
WHERE o.[type] = 'U'
ORDER BY STATS_DATE(i.[object_id], i.index_id) ASC OPTION (RECOMPILE);
-- Helps discover possible problems with out-of-date statistics
-- Also gives you an idea which indexes are most active
This query returns the name and several other
properties for every clustered and nonclustered index in your database,
sorted by the date on which statistics on that index were last updated.
This can help you track down performance problems caused by out of date
statistics that could be causing the SQL Server Query Optimizer to
choose a poorly performing execution plan. I like to use this query to
discover whether I have old statistics on my more volatile and
important tables in the database.
Unless you have a compelling reason not to, it is
usually a very good idea to have SQL Server automatically create
statistics and automatically update them as the data changes in your
tables. Especially for OLTP workloads, I usually like to enable the
Auto Update Statistics Asynchronously database setting, which allows
the Query Optimizer to use existing statistics while new ones are being
generated (instead of waiting for the new ones to be created). This can
give you more predictable query performance instead of taking a big
performance hit during a statistics update operation.
NOTE It
is also a good practice to manually update statistics on a periodic
basis as part of your regular database maintenance. Even under Auto
Update Statistics, statistics are not updated the moment data changes.
To keep the update frequency from conflicting with normal query
workloads, the auto update is only triggered when a certain threshold
of data change has occurred. Performing periodic manual statistics
updates ensures you always have up to date statistics.
Next, using the query shown in Listing 51, you will find out which indexes in the current database have the most fragmentation.
LISTING 51: Fragmentation information for all indexes
-- Get fragmentation info for all indexes
-- above a certain size in the current database
-- Note: This could take some time on a very large database
SELECT DB_NAME(database_id) AS [Database Name],
OBJECT_NAME(ps.OBJECT_ID) AS [Object Name],
i.name AS [Index Name], ps.index_id, index_type_desc,
avg_fragmentation_in_percent, fragment_count, page_count
FROM sys.dm_db_index_physical_stats(DB_ID(),NULL, NULL, NULL ,'LIMITED') AS ps
INNER JOIN sys.indexes AS i WITH (NOLOCK)
ON ps.[object_id] = i.[object_id]
AND ps.index_id= i.index_id
WHERE database_id= DB_ID()
AND page_count > 500
ORDER BY avg_fragmentation_in_percent DESC OPTION (RECOMPILE);
-- Helps determine whether you have fragmentation in your relational indexes
-- and how effective your index maintenance strategy is
This query returns every table and index in the
current database, ordered by average fragmentation level. It filters
out indexes that have fewer than 500 pages, as fragmentation in very
small tables is not something you typically have to worry about.
Depending on the size of your tables and indexes, and your hardware,
this query could take some time to run. This query uses the LIMITED
mode option (which is the default if no mode option is specified) when
it runs, so it will return less information, but take less time to run
than the DETAILED mode.
This query is useful because it can show you the
overall condition of your indexes as far as fragmentation goes
relatively quickly. Heavily fragmented indexes can reduce your I/O
performance and your query performance for some types of queries. It
can also increase the space required by your data files.
If you see indexes that have more than 10%
fragmentation, you need to decide whether to reorganize them or simply
rebuild them. Reorganizing an index is always an online operation, and
it can be stopped at any time. It can take longer than simply
rebuilding an index and it may not reduce the fragmentation as much as
rebuilding the index will. Rebuilding an index can be either an online
operation or an offline operation, depending on several factors. The
first factor is whether you have SQL Server Standard Edition or SQL
Server Enterprise Edition.
If you have Standard Edition, rebuilding an index
is always an offline operation. If you have Enterprise Edition, your
index rebuild operations can be online or offline depending on a few
more factors. With SQL Server 2012, you can rebuild clustered indexes
in online mode, regardless of what data types your table contains. With
earlier versions of SQL Server, you cannot rebuild a clustered index in
online mode if your table has any lob data types, such as nvarchar(max).
After you reorganize or rebuild indexes that are
heavily fragmented, you may free up a considerable amount of space
within your data file(s). The data file will still be the same size,
but more free space will be available. This is a good thing! Strongly
resist any urge you may have to shrink your data files to reclaim that
disk space. Shrinking data files is a very resource-intensive operation
that has the unfortunate side-effect of heavily fragmenting your
indexes. Do not let your system administrator or SAN administrator talk
you into shrinking data files or entire databases on a regular basis.
Finally, don’t make the common mistake
of simply rebuilding all your indexes on a regular basis, whether they
need it or not. This is a huge waste of resources on your database
server.