Best SQL Practices On Performance
Best SQL Practices On Performance
Performance Tuning
DB SQL Server 2008 R2
&
DB Maintenance Document
Performance Tuning & Maintenance Document Page 2 of 25
Revision History
Ver. # Description of
change
Sections Affected Created/
Modified By
Approved
By
Date of
issue
0.1 Initial Draft Alagarsamy T 23-Jan-13
Performance Tuning & Maintenance Document Page 3 of 25
Table of Contents
1 DB PERFORMANCE TUNING GUIDELINES ....................................................................................................... 4
2 CHECKLIST FOR ANALYZING SLOW-RUNNING QUERIES ............................................................................... 11
3 SQL MAINTENANCE ACTIVITY ...................................................................................................................... 12
3.1 TO CHECK THE MEMORY STATUS ........................................................................................................................ 12
3.2 TO CHECK THE UNUSED CACHE ........................................................................................................................... 12
3.3 TO PULL THE PHYSICAL MEMORY & SERVER INFORMATION ....................................................................................... 12
3.4 TO CALCULATE THE TPM .................................................................................................................................. 12
3.5 TO CHECK THE CURRENT CONCURRENT USERS (PROCESSES AT THAT TIME). ................................................................. 13
3.6 TO CHECK THE RAM MEMORY .......................................................................................................................... 13
3.7 TO IDENTIFICATION OF UNUSED INDEX................................................................................................................. 14
3.8 DRIVEN QUERIES (SAMPLE) ............................................................................................................................... 15
3.9 TABLE BUFFER USAGE ...................................................................................................................................... 15
3.10 RE-INDEXING.................................................................................................................................................. 17
3.11 REBUILD THE INDEX ......................................................................................................................................... 18
3.12 SCRIPTS FOR DEFRAGMENTATION OF THE INDEX .................................................................................................... 19
3.13 SCRIPTS FOR GENERATING DROP & CREATE INDEX ................................................................................................. 21
3.14 TO GET THE AGGREGATE PERFORMANCE STATISTICS FROM PLAN CACHE ..................................................................... 24
Performance Tuning & Maintenance Document Page 4 of 25
1 DB Performance Tuning Guidelines
1. Do not use OPENROWSET and OLEDB command anywhere in the SQL code.
2. For the bulk column fetch, first retrieve the main field based on the different condition and then
fetch all required columns based on the derived main fields.
a. E.g. In field status, first fetch the Work Orders & then fetch the details of Work Orders
b. Similarly, fetch the Job Id first in Technician Job Details & then fetch all the other
columns based on the Job id which was retrieved initially.
3. Always do not fetch all the grids in a screen. Analyse the screen and based on the result,
proceed for coding
a. E.g. In field status, there are three grids where we can display the output for
1. Job Summary
2. List View
3. Total Count
b. In this case, Job Summary & Total Count outputs will not vary unless we modify the
main input filter conditions.
c. For sorting any columns in the list view, we should not load the Job Summary & Total
Count columns again, since they would have loaded already. (UI changes are needed)
d. Based on the sorting, we can load only the List view.
4. Avoid unwanted where condition while framing SQL Query dynamically
a. E.g, if the Order Date input is passed as NULL, then we should not send any filter for
Order Date.
Donts:
and wo.priority_id = isnull(pi_priority_id,wo.priority_id)
Dos:
if @pi_priority_id is not null
begin
select @l_str = @l_str + ' and wo.priority_id =
'+convert(nvarchar(200),@pi_priority_id) +' '
end
5. Do not use functions in the left side of the where condition
a. E.g. where isnull(order_date,) between @pi_start and @pi_end
b. where cast(order_date as date) between @pi_start and @pi_end
c. where dbo.fn_Xdate(order_date) between @pi_start and @pi_end
Performance Tuning & Maintenance Document Page 5 of 25
6. All the images should be stored in Fileserver / Filestream. Also it would be better if it is read
from UI side.
7. Avoid using functions in where clause.
Donts:
and isnull(reviewer_id,0) = isnull(@pi_reviewer_id,0)
Dos:
and (reviewer_id is not null and reviewer_id = isnull(@pi_reviewer_id,0))
8. Join the different tables based on the volume from small to large
Donts:
from #Work_order_Result wo
join work_order wo1 (nolock) on wo1.work_order_id= wo.work_order_id
Dos:
From #work_order_result wo
join work_order wo1 (nolock) on wo.work_order_id= wo1.work_order_id
9. In a Join, keep inner join first, after that keep all the left join , It will reduce the IO Reads.
Donts:
from #Work_order_Result wo1
Join ref_work_item_type rwit (nolock)
on rwit.work_item_type_id = wo1.work_item_type_id
left join ref_organization ro (nolock) on ro.id_org = wo1.vendor_id
left join ref_priority rp (nolock) on rp.priority_id= wo1.priority_id
join vw_property_details re on re.Property_Id = wo1.Property_Id
Dos:
from #Work_order_Result wo1
Join ref_work_item_type rwit (nolock)
on rwit.work_item_type_id = wo1.work_item_type_id
join vw_property_details re on re.Property_Id = wo1.Property_Id
left join ref_organization ro (nolock) on ro.id_org = wo1.vendor_id
left join ref_priority rp (nolock) on rp.priority_id= wo1.priority_id
10. Use Nolock for well known masters for fetching stored procedures.
Donts:
Performance Tuning & Maintenance Document Page 6 of 25
from #Work_order_Result wo1
join ref_priority rp on rp.priority_id= wo1.priority_id
join vw_property_details re on re.Property_Id = wo1.Property_Id
Dos:
from #Work_order_Result wo1
join ref_priority rp (nolock) on rp.priority_id= wo1.priority_id
join vw_property_details re (noexpand) on re.Property_Id= wo1.Property_Id
11. Use Periodic Index, instead of reindex. With the help of Show Index Statistics, we should use
index defragment.
Eg:
USE FPREO;
GO
DBCC SHOW_STATISTICS ("ref_meta_data", pk_ref_meta_data_meta_data_id);
12. Always use covering index for non-clustered index.
Donts:
CREATE NONCLUSTERED INDEX IX_OrderDetailDateProdSold ON dbo.OrderDetail
( ProductID, OrderDate)
Dos:
CREATE NONCLUSTERED INDEX IX_OrderDetailDateProdSold ON dbo.OrderDetail
( ProductID, OrderDate) INCLUDE (QtySold);
13. If there is an index for any column, then check whether the read and write process are
performed by the respective index. It the read process does not happen for a long period, then
we do not need that index.
14. Try to have views for addresses, since we need to map many tables to fetch the address in
various queries. Please create an index for the respective views.
15. Also try to have views for Job details like Category, Service Type, Service Request in order to
avoid the joins for different tables in many queries.
16. Test every Execute Statements with the option with recompile
Eg: exec get_record_search_list_FS @pi_sp_name=N'[get_order_status]',
Performance Tuning & Maintenance Document Page 7 of 25
@sp_in_params=N'"null~null~null~null~null~null~null~null~null~Null~Null~Null~0, 5, 11, 12, 7,
4, 3, 2, 6, 9, 47, 8, 10~4~Null~null~null~null~1~null~null~null~1~Null~1~1"'
With Recompile
17. For every SP execution time , we need to verify the IO. Depending upon the IO , CPU Utilization
will differ.
Ex: Set Statistics IO on
EXEC SP Name
Set Statistics IO OFF
Logical read should be always less. Index for specific columns will help to reduce logical
Reads.
18. If a condition contains OR, then put that condition first
a. Where (wa.tech_id = @pi_tech_id or @pi_login_id = 1)
b. Please do write the above code as where (@pi_login_id = 1 or wa.tech_id =
@pi_tech_id)
19. If N select queries are mentioned in a SP, try to convert all the select queries into different SPs.
20. Remove Not in condition from all coding.
Donts:
and state_id not in (31,32,33,34)
Dos:
and state_id in (25,26,27,28)
21. Avoid use distinct key word
22. Create a function with SCHEMABINDING
Ex:
CREATE FUNCTION SchemaBinded(@INPUT INT)
RETURNS INT WITH SCHEMABINDING
BEGIN
RETURN @INPUT * 2 + 50
END
GO
http://www.mssqltips.com/sqlservertip/1692/using-schema-binding-to-improve-sql-server-udf-
performance/
23. To Reduce the IO of each stored procedures with the help of user defined function (use
Computed Columns /persisted Columns )
Performance Tuning & Maintenance Document Page 8 of 25
Ex:
alter table work_order add wo_category_id as
[dbo].[fn_wo_category_id](work_order_id) persisted
go
alter table work_order add wo_category_id as
[dbo].[fn_wo_category_id](work_order_id)
go - this is better than the above persited column
It will help to avoid Demoralization of the table.
24. Without Group by we will be able to get the No. of records count(*) Using
count(*) over( partition by (select 1)) as Total_Count
Record Count in the specific select statement itself.
25. XML is always faster than the CSV command. It will not have parsing methodology.
Ex:
declare @l_category nvarchar(max)
select @l_category =
substring(category ,1,len(category) -1)
from (
select
( select
convert(nvarchar(1000),wj1.work_order_category_
id ) + ','
from dbo.wo_job wj1 (nolock)
where wj1.work_order_id = wj.work_order_id
order by work_order_id
for xml path('') ) as category
from dbo.wo_job wj
where work_order_id = @pi_work_order_id
)a
26. Always use EXISTS query instead of NOT EXISTS.
Donts:
and not exists(select 1 from work_order wo1 (nolock)
where wo.work_order_id = wo1.work_order_id
and wo1.reviewer_id = @pi_login_id
)
Dos:
and exists(select 1 from work_order wo1 (nolock)
where wo.work_order_id = wo1.work_order_id
and wo1.reviewer_id = @pi_login_id )
27. If temporary tables are used in stored procedures, then drop temp table at the end of the sp.
Ex:
Performance Tuning & Maintenance Document Page 9 of 25
IF OBJECT_ID('tempdb..#temp_login') IS NOT NULL DROP TABLE #temp_login
28. Avoid using sub queries / joins with the same table in a stored procedure. Instead use EXISTS
which will improve the performance drastically.
Donts:
join ref_property re (nolock)
join ref_property re1 (nolock) on re1.property_id= re.property_id
and re1.property_stage_id in (1,2)
where re.property_id = @pi_property_id
Dos:
join ref_property re (nolock)
where re.property_id = @pi_property_id
and exists (select 1 ref_property re1 (nolock)
where re1.property_id = re.property_id
and re1.property_stage_id in (1,2)
)
29. Avoid using global temporary ##temp tables in a stored procedures. It will not support when
concurrent users perform simultaneously. i.e., same value will be passed to all users which leads
to wrong data entry.
Donts:
create table ##temp
( sl_no int identity(1,1),
property_id int
)
Dos:
create table #temp
( sl_no int identity(1,1),
property_id int
)
30. Avoid using SELECT INTO keyword for creating temp tables. Instead, create a temporary table
and then insert the records.
Donts:
Select id,designation
into #temp
from ref_designation
where is_active = 1
Dos:
Create table #temp (id bigint, designation nvarchar(400))
Performance Tuning & Maintenance Document Page 10 of 25
Insert into #temp (id, desigantion)
Select id,designation from ref_designation (nolock) where is_active = 1
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
31. While insertion, use column list in the insert statement. It will avoid unwanted errors when a
new column is added in the specified table.
Donts:
Insert into #temp
Select work_order_id, work_order_status_work_item_type
from work_order where work_order_id = @pi_work_order_id
Dos:
Insert into #temp (work_order_id, work_order_status_work_item_type)
Select work_order_id, work_order_status_work_item_type
from work_order (nolock) where work_order_id = @pi_work_order_id
32. Use NOEXPAND keyword, when views are used in joins.
EX:
from #Work_order_Result wo1
join ref_priority rp (nolock) on rp.priority_id= wo1.priority_id
join vw_property_details re (noexpand) on re.Property_Id= wo1.Property_Id
33. Avoid selecting records by using * keyword in stored procedures. Instead, provide the column
list which needs to be given as output.
Donts:
Select * from ref_property (nolock) where property_id = @pi_property_id
Dos:
Select property_id, property_code, property_number,priority_id,is_active
from ref_property (nolock) where property_id = @pi_property_id
34. Instead of including Nolock for all the tables in the stored procedure, we can set the isolation level
as SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED in the top of stored procedures.
Performance Tuning & Maintenance Document Page 11 of 25
It will play the exact role of Nolock .It refers all the tables which referred in the respective stored
procedure. It would be applicable for the connections (Session ID) even though the respective Stored
procedures contains the nested Stored procedures/Remote Procedure. This should applicable for only
fetch stored procedures.
2 Checklist for Analyzing Slow-Running Queries
Slow network communication.
Inadequate memory in the server computer, or not enough memory available for SQL Server.
Lack of useful statistics
Lack of useful indexes.
Lack of useful indexed views.
Lack of useful data striping.
Lack of useful partitioning.
Reference: http://msdn.microsoft.com/en-us/library/ms177500.aspx
Performance Tuning & Maintenance Document Page 12 of 25
3 SQL Maintenance Activity
3.1 To Check the memory Status
DBCC MEMORYSTATUS
3.2 To Check the unused cache
DBCC FREESYSTEMCACHE ('ALL') WITH MARK_IN_USE_FOR_REMOVAL;
3.3 To pull the physical memory & server information
select * from sys.dm_os_sys_info
select * from sys.dm_os_sys_memory
select convert(numeric(5,2),(total_physical_memory_kb/1024.0/1024.0)) as
Total, convert(numeric(5,2),(available_physical_memory_kb/1024.0/1024.0))
as Available, system_memory_state_desc ,( Select
((bpool_committed*8)/1024.0/1024.0) from sys.dm_os_sys_info (nolock) ) As
SQLUseage from sys.dm_os_sys_memory (nolock)
3.4 To Calculate the TPM
DECLARE @cntr_value1 bigint
DECLARE @cntr_value2 bigint
t:
SELECT @cntr_value1 = cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = 'transactions/sec'
AND object_name = 'SQLServer:Databases'
Performance Tuning & Maintenance Document Page 13 of 25
AND instance_name = 'FPREOPRO'
WAITFOR DELAY '00:00:01'
SELECT @cntr_value2 = cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name = 'transactions/sec'
AND object_name = 'SQLServer:Databases'
AND instance_name = 'FPREOPRO'
insert into mem_transaction_counter
Select @cntr_value2-@cntr_value1,getdate()
goto t
3.5 To check the current concurrent users (Processes at that time).
select GETDATE() as 'Time',COUNT(*) as 'Connection_count'
from master.dbo.sysprocesses p
join master.dbo.sysdatabases d on p.dbID = d.dbID
where p.dbid = db_id()
3.6 To check the RAM Memory
SELECT GETDATE() As Time, physical_memory_in_bytes/1073741824.0 as
[Physical Memory_GB] FROM sys.dm_os_sys_info
Performance Tuning & Maintenance Document Page 14 of 25
3.7 To Identification of unused Index
Scan: An index scan is a complete read of all of the leaf pages in the index.
Seek: An index seeks is an operation where SQL uses the b-tree structure to locate either a specific
value or the beginning of a range of value
If both are 0 then the index is useless.
1)
WITH indexstats ([Table],[Index],[Reads],[Writes],[Rows]) AS ( SELECT
usr.[name] + '.' + obj.[name] [Table], ixs.[name] [Index] ,
usage.user_seeks + usage.user_scans + usage.user_lookups [Reads],
usage.[user_updates] [Writes],
(SELECT SUM(sp.[rows]) FROM sys.partitions sp
WHERE usage.OBJECT_ID = sp.object_id AND sp.index_id = usage.index_id)
[Rows]
FROM sys.dm_db_index_usage_stats usage INNER JOIN sys.indexes ixs ON
usage.[object_id] = ixs.[object_id]
AND ixs.[index_id] = usage.[index_id] INNER JOIN sys.objects obj ON
usage.[object_id] = obj.[object_id]
INNER JOIN sys.sysusers usr ON obj.[schema_id] = usr.[uid] WHERE
usage.database_id = DB_ID()
AND usage.index_id > 0 AND OBJECTPROPERTY(usage.[object_id],
'IsUserTable') = 1 )
SELECT 'Drop Index ' +[INDEX] + ' on ' +[table],* FROM indexstats WHERE
Reads = 0 and ([index] not like '%pk_%' and [index] not like '%uk_%'
)ORDER BY [Rows] DESC, [Index]
go
2)
DECLARE @dbid INT
SELECT @dbid = DB_ID(DB_NAME())
SELECT OBJECTNAME = OBJECT_NAME(I.OBJECT_ID),
INDEXNAME = I.NAME,
I.INDEX_ID
FROM SYS.INDEXES I
JOIN SYS.OBJECTS O
ON I.OBJECT_ID = O.OBJECT_ID
WHERE OBJECTPROPERTY(O.OBJECT_ID,'IsUserTable') = 1
AND I.INDEX_ID NOT IN (
SELECT S.INDEX_ID
FROM SYS.DM_DB_INDEX_USAGE_STATS S
WHERE S.OBJECT_ID = I.OBJECT_ID
AND I.INDEX_ID = S.INDEX_ID
AND DATABASE_ID = @dbid)
ORDER BY OBJECTNAME,
I.INDEX_ID,
INDEXNAME ASC
GO
Performance Tuning & Maintenance Document Page 15 of 25
3.8 To Identification of Missing Index
SELECT mid.statement ,migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) *
(migs.user_seeks + migs.user_scans) AS
improvement_measure,OBJECT_NAME(mid.Object_id),'CREATE INDEX [idx_' + CONVERT (varchar,
mig.index_group_handle) + '_' + CONVERT (varchar, mid.index_handle) + '_' +LEFT
(PARSENAME(mid.statement, 1), 32) + ']' + ' ON ' + mid.statement + ' (' + ISNULL
(mid.equality_columns,'') + CASE WHEN mid.equality_columns IS NOT NULL AND
mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END + ISNULL (mid.inequality_columns,
'') + ')' + ISNULL (' INCLUDE (' + mid.included_columns + ')', '') AS
create_index_statement, migs.*, mid.database_id, mid.[object_id] FROM
sys.dm_db_missing_index_groups mig JOIN sys.dm_db_missing_index_group_stats migs ON
migs.group_handle = mig.index_group_handle JOIN sys.dm_db_missing_index_details mid ON
mig.index_handle = mid.index_handle WHERE migs.avg_total_user_cost *
(migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) > 10 ORDER BY
migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans)
DESC
3.9 Driven Queries (Sample)
Optimize Parameter Driven Queries with SQL Server OPTIMIZE FOR Hint:
DECLARE @Country VARCHAR(20)
SET @Country = 'US'
SELECT *
FROM Sales.SalesOrderHeader h, Sales.Customer c,
Sales.SalesTerritory t
WHERE h.CustomerID = c.CustomerID
AND c.TerritoryID = t.TerritoryID
AND CountryRegionCode = @Country
OPTION (OPTIMIZE FOR (@Country = 'US'))
Reference for the above:
http://www.mssqltips.com/sqlservertip/1354/optimize-parameter-
driven-queries-with-sql-server-optimize-for-hint/
3.10 Table Buffer Usage
SELECT
obj.[name],
Performance Tuning & Maintenance Document Page 16 of 25
i.[name],
i.[type_desc],
count(*)AS Buffered_Page_Count ,
count(*) * 8192.0 / (1024 * 1024) as Buffer_MB
FROM sys.dm_os_buffer_descriptors AS bd
INNER JOIN
(
SELECT object_name(object_id) AS name
,index_id ,allocation_unit_id, object_id
FROM sys.allocation_units AS au
INNER JOIN sys.partitions AS p
ON au.container_id = p.hobt_id
AND (au.type = 1 OR au.type = 3)
UNION ALL
SELECT object_name(object_id) AS name
,index_id, allocation_unit_id, object_id
FROM sys.allocation_units AS au
INNER JOIN sys.partitions AS p
ON au.container_id = p.hobt_id
AND au.type = 2
) AS obj
ON bd.allocation_unit_id = obj.allocation_unit_id
LEFT JOIN sys.indexes i on i.object_id = obj.object_id AND i.index_id =
obj.index_id
WHERE database_id = db_id()
GROUP BY obj.name, obj.index_id , i.[name],i.[type_desc]
ORDER BY Buffered_Page_Count DESC
Performance Tuning & Maintenance Document Page 17 of 25
3.11 Re-Indexing
CREATE PROCEDURE upd_reindex
AS
begin
set nocount on
declare @po_error_code nvarchar(4000),
@po_severity tinyint ,
@l_incr int ,
@l_count int ,
@l_table_name nvarchar(200)
--if exists(select 1 from sys.tables where name = 'TableRowCount')
--begin
-- drop table [TableRowCount]
--end
--CREATE TABLE [TableRowCount](
-- TableName sysname,
-- [TableRowCount] int )
----EXEC sp_MSForEachTable 'INSERT [TableRowCount](TableName,
[TableRowCount]) SELECT ''?'', COUNT(*) FROM ?'
--EXEC sp_MSforeachtable @command1 = "print '?' DBCC DBREINDEX ('?', '
', 80)"
--EXEC sp_updatestats
--select * from [TableRowCount]
create table #temp_reindex
(
sl_no int ,
table_name nvarchar(200)
)
insert into #temp_reindex
(
sl_no,
table_name
)
SELECT row_number() over(order by name),
name
FROM sys.tables
where type = 'U'
if exists (select 1 from #temp_reindex)
Performance Tuning & Maintenance Document Page 18 of 25
begin
select @l_incr = 1
select @l_count = COUNT(*) from #temp_reindex
while @l_incr < = @l_count
begin
select @l_table_name = table_name
from #temp_reindex tp
where sl_no = @l_incr
PRINT 'Reindexing Table: ' + @l_table_name
DBCC DBREINDEX(@l_table_name, '', 80)
select @l_incr = @l_incr + 1
select @l_table_name = null
end
end
EXEC sp_updatestats
set nocount off
end
go
3.12 Rebuild the Index
create procedure rebuild_index
as
begin
DECLARE @Database VARCHAR(255)
DECLARE @Table VARCHAR(255)
DECLARE @cmd NVARCHAR(500)
DECLARE @fillfactor INT
SET @fillfactor = 90
DECLARE DatabaseCursor CURSOR FOR
SELECT name FROM MASTER.dbo.sysdatabases
WHERE dbid = db_id()
ORDER BY 1
OPEN DatabaseCursor
FETCH NEXT FROM DatabaseCursor INTO @Database
WHILE @@FETCH_STATUS = 0
BEGIN
Performance Tuning & Maintenance Document Page 19 of 25
SET @cmd = 'DECLARE TableCursor CURSOR FOR SELECT ''['' +
table_catalog + ''].['' + table_schema + ''].['' +
table_name + '']'' as tableName FROM ' + @Database +
'.INFORMATION_SCHEMA.TABLES
WHERE table_type = ''BASE TABLE'''
-- create table cursor
EXEC (@cmd)
OPEN TableCursor
FETCH NEXT FROM TableCursor INTO @Table
WHILE @@FETCH_STATUS = 0
BEGIN
IF (@@MICROSOFTVERSION / POWER(2, 24) >= 9)
BEGIN
-- SQL 2005 or higher command
SET @cmd = 'ALTER INDEX ALL ON ' + @Table + ' REBUILD WITH
(FILLFACTOR = ' + CONVERT(VARCHAR(3),@fillfactor) + ')'
EXEC (@cmd)
END
ELSE
BEGIN
-- SQL 2000 command
DBCC DBREINDEX(@Table,' ',@fillfactor)
END
FETCH NEXT FROM TableCursor INTO @Table
END
CLOSE TableCursor
DEALLOCATE TableCursor
FETCH NEXT FROM DatabaseCursor INTO @Database
END
CLOSE DatabaseCursor
DEALLOCATE DatabaseCursor
end
3.13 Scripts for Defragmentation of the Index
create procedure defragment_index
as
begin
SET NOCOUNT ON;
DECLARE @tablename varchar(255);
DECLARE @execstr varchar(400);
DECLARE @objectid int;
DECLARE @indexid int;
DECLARE @frag decimal;
DECLARE @maxfrag decimal;
-- Decide on the maximum fragmentation to allow for.
SELECT @maxfrag = 30.0;
Performance Tuning & Maintenance Document Page 20 of 25
-- Declare a cursor.
DECLARE tables CURSOR FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE';
-- Create the table.
CREATE TABLE #fraglist (
ObjectName char(255),
ObjectId int,
IndexName char(255),
IndexId int,
Lvl int,
CountPages int,
CountRows int,
MinRecSize int,
MaxRecSize int,
AvgRecSize int,
ForRecCount int,
Extents int,
ExtentSwitches int,
AvgFreeBytes int,
AvgPageDensity int,
ScanDensity decimal,
BestCount int,
ActualCount int,
LogicalFrag decimal,
ExtentFrag decimal);
-- Open the cursor.
OPEN tables;
-- Loop through all the tables in the database.
FETCH NEXT
FROM tables
INTO @tablename;
WHILE @@FETCH_STATUS = 0
BEGIN
-- Do the showcontig of all indexes of the table
INSERT INTO #fraglist
EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''')
WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS');
FETCH NEXT
FROM tables
INTO @tablename;
END;
-- Close and deallocate the cursor.
CLOSE tables;
DEALLOCATE tables;
-- Declare the cursor for the list of indexes to be defragged.
DECLARE indexes CURSOR FOR
SELECT ObjectName, ObjectId, IndexId, LogicalFrag
Performance Tuning & Maintenance Document Page 21 of 25
FROM #fraglist
WHERE LogicalFrag >= @maxfrag
AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0;
-- Open the cursor.
OPEN indexes;
-- Loop through the indexes.
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'Executing DBCC INDEXDEFRAG (0, ' + RTRIM(@tablename) + ',
' + RTRIM(@indexid) + ') - fragmentation currently '
+ RTRIM(CONVERT(varchar(15),@frag)) + '%';
SELECT @execstr = 'DBCC INDEXDEFRAG (0, ' + RTRIM(@objectid) + ',
' + RTRIM(@indexid) + ')';
EXEC (@execstr);
FETCH NEXT
FROM indexes
INTO @tablename, @objectid, @indexid, @frag;
END;
-- Close and deallocate the cursor.
CLOSE indexes;
DEALLOCATE indexes;
-- Delete the temporary table.
DROP TABLE #fraglist;
end
3.14 Scripts for generating Drop & Create Index
SELECT
ixz.object_id,
tablename = QUOTENAME(scmz.name) + '.' +
QUOTENAME((OBJECT_NAME(ixz.object_id))),
tableid = ixz.object_id,
indexid = ixz.index_id,
indexname = ixz.name,
isunique = INDEXPROPERTY (ixz.object_id,ixz.name,'isunique'),
isclustered = INDEXPROPERTY (ixz.object_id,ixz.name,'isclustered'),
indexfillfactor = INDEXPROPERTY
(ixz.object_id,ixz.name,'indexfillfactor'),
--SQL2008+ Filtered indexes:
CASE
WHEN ixz.filter_definition IS NULL
THEN ''
ELSE ' WHERE ' + ixz.filter_definition
END Filter_Definition
Performance Tuning & Maintenance Document Page 22 of 25
--For 2005, which did not have filtered indexes, comment out the above
CASE statement, and uncomment this:
INTO #tmp_indexes
FROM sys.indexes ixz
INNER JOIN sys.objects obz
ON ixz.object_id = obz.object_id
INNER JOIN sys.schemas scmz
ON obz.schema_id = scmz.schema_id
WHERE ixz.index_id > 0
AND ixz.index_id < 255 ---- 0 = HEAP index, 255 = TEXT columns index
AND INDEXPROPERTY (ixz.object_id,ixz.name,'ISUNIQUE') = 0 -- comment out
to include unique and
AND INDEXPROPERTY (ixz.object_id,ixz.name,'ISCLUSTERED') = 0 -- comment
out to include PK's
ALTER TABLE #tmp_indexes ADD keycolumns VARCHAR(4000), includes
VARCHAR(4000)
GO
DECLARE @isql_key VARCHAR(4000),
@isql_incl VARCHAR(4000),
@tableid INT,
@indexid INT
DECLARE index_cursor CURSOR
FOR
SELECT
tableid,
indexid
FROM #tmp_indexes
OPEN index_cursor
FETCH NEXT FROM index_cursor INTO @tableid, @indexid
WHILE @@FETCH_STATUS <> -1
BEGIN
SELECT @isql_key = '', @isql_incl = ''
SELECT --ixz.name, colz.colid, colz.name, ixcolz.index_id,
ixcolz.object_id, *
--key column
@isql_key = CASE ixcolz.is_included_column
WHEN 0
THEN CASE ixcolz.is_descending_key
WHEN 1
THEN @isql_key + COALESCE(colz.name,'') + ' DESC, '
ELSE @isql_key + COALESCE(colz.name,'') + ' ASC, '
END
ELSE @isql_key
END,
--include column
@isql_incl = CASE ixcolz.is_included_column
WHEN 1
Performance Tuning & Maintenance Document Page 23 of 25
THEN CASE ixcolz.is_descending_key
WHEN 1
THEN @isql_incl + COALESCE(colz.name,'') + ', '
ELSE @isql_incl + COALESCE(colz.name,'') + ', '
END
ELSE @isql_incl
END
FROM sysindexes ixz
INNER JOIN sys.index_columns AS ixcolz
ON (ixcolz.column_id > 0
AND ( ixcolz.key_ordinal > 0
OR ixcolz.partition_ordinal = 0
OR ixcolz.is_included_column != 0)
)
AND ( ixcolz.index_id=CAST(ixz.indid AS INT)
AND ixcolz.object_id=ixz.id
)
INNER JOIN sys.columns AS colz
ON colz.object_id = ixcolz.object_id
AND colz.column_id = ixcolz.column_id
WHERE ixz.indid > 0 AND ixz.indid < 255
AND (ixz.status & 64) = 0
AND ixz.id = @tableid
AND ixz.indid = @indexid
ORDER BY
ixz.name,
CASE ixcolz.is_included_column
WHEN 1
THEN ixcolz.index_column_id
ELSE ixcolz.key_ordinal
END
--remove any trailing commas from the cursor results
IF LEN(@isql_key) > 1 SET @isql_key = LEFT(@isql_key, LEN(@isql_key) -1)
IF LEN(@isql_incl) > 1 SET @isql_incl = LEFT(@isql_incl, LEN(@isql_incl)
-1)
--put the columns collection into our temp table
UPDATE #tmp_indexes
SET keycolumns = @isql_key,
includes = @isql_incl
WHERE tableid = @tableid
AND indexid = @indexid
FETCH NEXT FROM index_cursor INTO @tableid,@indexid
END --WHILE
CLOSE index_cursor
DEALLOCATE index_cursor
--remove invalid indexes, ie ones without key columns
DELETE FROM #tmp_indexes WHERE keycolumns = ''
SET NOCOUNT ON
SELECT
'IF NOT EXISTS(SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'''
+ ti.TABLENAME + '''' + ') AND name = N' + '''' + ti.INDEXNAME + '''' +
')' + ' ' +
Performance Tuning & Maintenance Document Page 24 of 25
'CREATE '
+ CASE WHEN ti.ISUNIQUE = 1 THEN 'UNIQUE ' ELSE '' END
+ CASE WHEN ti.ISCLUSTERED = 1 THEN 'CLUSTERED ' ELSE '' END
+ 'INDEX ' + QUOTENAME(ti.INDEXNAME)
+ ' ON ' + (ti.TABLENAME) + ' '
+ '(' + ti.keycolumns + ')'
+ CASE
WHEN ti.INDEXFILLFACTOR = 0 AND ti.ISCLUSTERED = 1 AND INCLUDES = '' THEN
ti.Filter_Definition + ' WITH (SORT_IN_TEMPDB = ON) ON [' + fg.name + ']'
WHEN INDEXFILLFACTOR = 0 AND ti.ISCLUSTERED = 0 AND ti.INCLUDES = '' THEN
ti.Filter_Definition + ' WITH (ONLINE = ON, SORT_IN_TEMPDB = ON) ON [' +
fg.name + ']'
WHEN INDEXFILLFACTOR <> 0 AND ti.ISCLUSTERED = 0 AND ti.INCLUDES = ''
THEN ti.Filter_Definition + ' WITH (ONLINE = ON, SORT_IN_TEMPDB = ON,
FILLFACTOR = ' + CONVERT(VARCHAR(10),ti.INDEXFILLFACTOR) + ') ON [' +
fg.name + ']'
WHEN INDEXFILLFACTOR = 0 AND ti.ISCLUSTERED = 0 AND ti.INCLUDES <> ''
THEN ' INCLUDE (' + ti.INCLUDES + ') ' + ti.Filter_Definition + ' WITH
(ONLINE = ON, SORT_IN_TEMPDB = ON) ON [' + fg.name + ']'
ELSE ' INCLUDE(' + ti.INCLUDES + ') ' + ti.Filter_Definition + ' WITH
(FILLFACTOR = ' + CONVERT(VARCHAR(10),ti.INDEXFILLFACTOR) + ', ONLINE =
ON, SORT_IN_TEMPDB = ON) ON [' + fg.name + ']'
END
FROM #tmp_indexes ti
JOIN sys.indexes i ON ti.Object_id = i.object_id and ti.indexname =
i.name
JOIN sys.filegroups fg on i.data_space_id = fg.data_space_id
WHERE LEFT(ti.tablename,3) NOT IN ('sys', 'dt_') --exclude system tables
ORDER BY
ti.tablename,
ti.indexid,
ti.indexname
--makes the drop
SELECT
'DROP INDEX '
+ ' ' + (tablename) + '.'
+ (indexname) + ''
FROM #tmp_indexes
WHERE LEFT(tablename,4) NOT IN ('[sys', 'dt_')
----Drop the temp table again
DROP TABLE #tmp_indexes
3.15 To get the aggregate performance statistics from Plan Cache
SELECT total_logical_reads, total_logical_writes,
total_physical_reads, total_worker_time,
total_elapsed_time, sys.dm_exec_sql_text.TEXT
FROM sys.dm_exec_query_stats
Performance Tuning & Maintenance Document Page 25 of 25
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
WHERE total_logical_reads <> 0
AND total_logical_writes <> 0
ORDER BY (total_logical_reads + total_logical_writes) DESC