3/30/2025 SQL Server Scripts for
Performance Tuning
Asfaw Gedamu
SQL Server Scripts for Performance Tuning
Are sluggish queries, deadlocks, or mysterious CPU spikes keeping you up at night?
Performance tuning is like detective work. Without the right tools, you’re stuck chasing ghosts.
This 20-script guide cuts through the noise. From pinpointing long-running queries to crushing
index fragmentation and slashing deadlocks, these scripts are your Swiss Army knife for SQL
Server performance. No fluff, no theory, just actionable code to transform chaos into clarity.
Always test scripts in non-production environments first!
1. How to find long running sql scripts
SELECT
TOP 10 SUBSTRING(qt.TEXT, (qs.statement_start_offset/2) + 1,
((CASE statement_end_offset
WHEN -1 THEN DATALENGTH(qt.TEXT)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2) + 1),
qs.execution_count,
qs.total_logical_reads, qs.last_logical_reads,
qs.total_logical_writes, qs.last_logical_writes,
qs.total_worker_time,
qs.last_worker_time,
qs.total_elapsed_time/1000000 total_elapsed_time_in_sec,
qs.last_elapsed_time/1000000 last_elapsed_time_in_sec,
qs.last_execution_time,
qp.query_plan
FROM
sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY qs.total_logical_reads DESC -- logical reads
-- ORDER BY qs.total_logical_writes DESC -- logical writes
-- ORDER BY qs.total_worker_time DESC -- CPU time
1. How to check index fragmentation
SELECT
dbschemas.[name] as 'Schema',
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()
ORDER BY
indexstats.avg_fragmentation_in_percent DESC
2. How to check blocked processes
SELECT
blocking_session_id AS BlockingSessionID,
session_id AS VictimSessionID,
(SELECT text FROM sys.dm_exec_sql_text(sql_handle)) AS
SQLQuery
FROM sys.dm_exec_requests
WHERE blocking_session_id > 0
3. How to extract wait statistics
WITH Waits AS
(SELECT
wait_type,
wait_time_ms / 1000. AS wait_time_s,
100. * wait_time_ms / SUM(wait_time_ms) OVER() AS pct,
ROW_NUMBER() OVER(ORDER BY wait_time_ms DESC) AS rn
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
'CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE',
'SLEEP_TASK',
'SLEEP_SYSTEMTASK', 'SQLTRACE_BUFFER_FLUSH', 'WAITFOR',
'LOGMGR_QUEUE',
'CHECKPOINT_QUEUE', 'REQUEST_FOR_DEADLOCK_SEARCH',
'XE_TIMER_EVENT', 'BROKER_TO_FLUSH',
'BROKER_TASK_STOP', 'CLR_MANUAL_EVENT', 'CLR_AUTO_EVENT',
'DISPATCHER_QUEUE_SEMAPHORE',
'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT',
'FT_IFTSHC_MUTEX', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP')
)
SELECT
W1.wait_type,
CAST(W1.wait_time_s AS DECIMAL(12, 2)) AS wait_time_s,
CAST(W1.pct AS DECIMAL(12, 2)) AS pct,
CAST(SUM(W2.pct) AS DECIMAL(12, 2)) AS running_pct
FROM Waits AS W1
INNER JOIN Waits AS W2 ON W2.rn <= W1.rn
GROUP BY W1.rn, W1.wait_type, W1.wait_time_s, W1.pct
HAVING SUM(W2.pct) - W1.pct < 95; -- percentage threshold
4. How to find missing indexes
SELECT
dm_mid.database_id,
dm_mid.[object_id],
dm_migs.avg_total_user_cost * (dm_migs.avg_user_impact /
100.0) * (dm_migs.user_seeks + dm_migs.user_scans) AS
improvement_measure,
'CREATE INDEX missing_index_' + CONVERT (varchar,
dm_mid.index_group_handle) + '_' + CONVERT (varchar,
dm_mid.index_handle) + ' ON ' + dm_mid.statement + ' (' + ISNULL
(dm_mic.column_store, '') + ')' + ISNULL
(dm_mic.equality_columns,'')
+ CASE WHEN dm_mic.equality_columns IS NOT NULL AND
dm_mic.inequality_columns IS NOT NULL THEN ',' ELSE '' END +
ISNULL (dm_mic.inequality_columns, '') + ')' + ISNULL (' INCLUDE
(' + dm_mic.included_columns + ')', '') AS
create_index_statement,
dm_migs.*,
dm_mid.database_id,
dm_mid.[object_id]
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
INNER JOIN (
SELECT
index_handle,
STRING_AGG(column_name, ', ') WITHIN GROUP (ORDER BY
column_name) AS column_store
FROM sys.dm_db_missing_index_columns (NULL)
WHERE column_usage = 'STORE'
GROUP BY index_handle
) AS dm_mic_store ON dm_mid.index_handle =
dm_mic_store.index_handle
LEFT JOIN (
SELECT
index_handle,
STRING_AGG(column_name, ', ') WITHIN GROUP (ORDER BY
column_name) AS equality_columns,
STRING_AGG(column_name, ', ') WITHIN GROUP (ORDER BY
column_name) AS inequality_columns,
STRING_AGG(column_name, ', ') WITHIN GROUP (ORDER BY
column_name) AS included_columns
FROM sys.dm_db_missing_index_columns (NULL)
WHERE column_usage = 'EQUALITY' OR column_usage =
'INEQUALITY' OR column_usage = 'INCLUDE'
GROUP BY index_handle
) AS dm_mic ON dm_mid.index_handle = dm_mic.index_handle
WHERE
dm_mid.database_id = DB_ID()
ORDER BY
improvement_measure DESC
5. How to monitor CPU utilization
SELECT
SQLProcessUtilization AS [SQL Server Process CPU
Utilization],
SystemIdle AS [System Idle Process],
100 - SystemIdle - SQLProcessUtilization AS [Other Process
CPU Utilization]
FROM
(SELECT
record_id,
dateadd(ms, -1 * ((sys.ms_ticks / 1000) - [timestamp]),
GetDate()) as [Event Time],
SQLProcessUtilization,
SystemIdle
FROM
(SELECT
record_id,
sys.ms_ticks,
[timestamp],
convert(xml, record) as [record]
FROM sys.dm_os_ring_buffers
CROSS JOIN sys.dm_os_sys_info sys
WHERE ring_buffer_id = 1
AND record_id > (SELECT MAX(record_id) FROM
sys.dm_os_ring_buffers WHERE ring_buffer_id = 1) - 60
) AS x
) AS y
ORDER BY record_id DESC;
6. How to query buffer cache hit ratio
SELECT
CAST((COUNT(*) * 100) AS DECIMAL(5,2)) AS
BufferCacheHitRatio
FROM sys.dm_os_performance_counters
WHERE object_name = 'SQLServer:Buffer Manager'
AND counter_name = 'Buffer cache hit ratio'
7. How to find page life expectancy
SELECT
[object_name],
counter_name,
cntr_value
FROM
sys.dm_os_performance_counters
WHERE
[object_name] LIKE '%Manager%'
AND counter_name = 'Page life expectancy'
8. How to query database size and free space
EXEC sp_MSforeachdb N'USE [?];
SELECT
DB_NAME() AS [DatabaseName],
file_id,
type_desc AS [FileType],
name AS [LogicalName],
Physical_Name AS [PhysicalName],
(size * 8.0 / 1024) AS [SizeMB],
(FILEPROPERTY(name, ''SpaceUsed'') * 8.0 / 1024) AS
[SpaceUsedMB],
((size - FILEPROPERTY(name, ''SpaceUsed'')) * 8.0 / 1024) AS
[FreeSpaceMB]
FROM sys.master_files
WHERE
DB_NAME(database_id) = DB_NAME()
ORDER BY
type, file_id;
9. How to identify expensive queries
SELECT
TOP 5 total_logical_reads,
total_logical_writes,
total_physical_reads, total_physical_reads,
execution_count,
total_logical_reads + total_logical_writes AS [Aggregated
I/O],
total_elapsed_time,
statement_start_offset,
statement_end_offset,
plan_handle,
sql_handle
FROM
sys.dm_exec_requests
ORDER BY
[Aggregated I/O] DESC;
10. How to find deadlock process
SELECT
XEvent.query('(data/value/deadlock)[1]') AS DeadlockGraph
FROM
(SELECT
XEvent.query('.') AS XEvent
FROM
(SELECT
CAST(target_data AS XML) AS TargetData
FROM
sys.dm_xe_session_targets st
JOIN
sys.dm_xe_sessions s ON s.address =
st.event_session_address
WHERE
name = 'system_health') AS Data
CROSS APPLY
TargetData.nodes ('//RingBufferTarget/event') AS
XEventData (XEvent)
) AS src
WHERE
XEvent.value('(@name)[1]', 'varchar(4000)') =
'xml_deadlock_report';
11. How to find blocked processes
SELECT
blocked_session_id,
blocking_session_id,
wait_type,
wait_duration_ms,
resource_description,
(SELECT text FROM sys.dm_exec_sql_text(sql_handle)) AS
sql_text
FROM
sys.dm_os_waiting_tasks
WHERE
blocking_session_id IS NOT NULL;
12. How to query active transactions and locks
SELECT
at.transaction_id,
at.name AS transaction_name,
at.transaction_begin_time,
at.transaction_state,
at.transaction_status,
tl.request_session_id,
tl.resource_type,
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_status
FROM
sys.dm_tran_active_transactions at
JOIN
sys.dm_tran_locks tl ON at.transaction_id =
tl.request_owner_id;
13. How to query all backups in a database
SELECT
database_name AS [Database],
backup_start_date AS [Backup Start],
backup_finish_date AS [Backup Finish],
DATEDIFF(second, backup_start_date, backup_finish_date) AS
[Duration (s)],
backup_size/1024/1024 AS [Size (MB)],
[type] AS [Backup Type]
FROM
msdb.dbo.backupset
WHERE
database_name = 'YourDatabaseName' -- Specify your database
name here
ORDER BY
backup_start_date DESC;
14. How to query a database with no backups
WITH LastBackup AS (
SELECT
database_name,
MAX(backup_finish_date) AS last_backup
FROM
msdb.dbo.backupset
GROUP BY
database_name
)
SELECT
name AS [Database]
FROM
sys.databases db
LEFT JOIN
LastBackup lb ON db.name = lb.database_name
WHERE
db.state = 0 -- Only consider online databases
AND (
lb.last_backup IS NULL
OR DATEDIFF(hour, lb.last_backup, GETDATE()) > 48 --
Consider as not recent if more than 48 hours old
)
ORDER BY
db.name;
15. How to query and rebuild fragmented indexes
--query fragmented indexes
SELECT
dbschemas.[name] as 'Schema',
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()
ORDER BY
indexstats.avg_fragmentation_in_percent desc;
--build index
ALTER INDEX [YourIndexName] ON [YourSchema].[YourTableName]
REBUILD;
--reorganize indexes
ALTER INDEX [YourIndexName] ON [YourSchema].[YourTableName]
REORGANIZE;
--check index statistics
UPDATE STATISTICS [YourSchema].[YourTableName] [YourIndexName]
--rebuild all indexes
EXEC sp_MSForEachTable 'ALTER INDEX ALL ON ? REBUILD'
16. Rebuild or reorganize indexes based on fragmentation levels conditionally
DECLARE @TableName NVARCHAR(255)
DECLARE @SchemaName NVARCHAR(255)
DECLARE @IndexName NVARCHAR(255)
DECLARE @FragPercent DECIMAL
DECLARE IndexCursor CURSOR FOR
SELECT
OBJECT_SCHEMA_NAME(ips.[object_id]) as SchemaName,
OBJECT_NAME(ips.[object_id]) as TableName,
si.name as IndexName,
ips.avg_fragmentation_in_percent
FROM
sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL,
NULL, 'LIMITED') ips
JOIN
sys.indexes si ON ips.[object_id] = si.[object_id]
AND ips.index_id = si.index_id
WHERE
ips.avg_fragmentation_in_percent > 10 -- Consider
indexes with more than 10% fragmentation
ORDER BY
ips.avg_fragmentation_in_percent DESC
OPEN IndexCursor
FETCH NEXT FROM IndexCursor INTO @SchemaName, @TableName,
@IndexName, @FragPercent
WHILE @@FETCH_STATUS = 0
BEGIN
IF @FragPercent > 30
BEGIN
EXEC('ALTER INDEX [' + @IndexName + '] ON [' +
@SchemaName + '].[' + @TableName + '] REBUILD')
END
ELSE
BEGIN
EXEC('ALTER INDEX [' + @IndexName + '] ON[' +
@SchemaName + '].[' + @TableName + '] REORGANIZE')
END
FETCH NEXT FROM IndexCursor INTO @SchemaName, @TableName,
@IndexName, @FragPercent
END
CLOSE IndexCursor
DEALLOCATE IndexCursor
17. How to query partition functions and partition schemes
--list partition functions
SELECT
name AS PartitionFunction,
type_desc,
fanout,
create_date,
modify_date
FROM
sys.partition_functions;
--list partition schemes
SELECT
name AS PartitionScheme,
type_desc,
fanout,
create_date,
modify_date
FROM
sys.partition_schemes;
18. Check portioned tables and indexes
SELECT
OBJECT_NAME(i.object_id) AS TableName,
i.name AS IndexName,
p.partition_number,
fg.name AS FileGroupName,
p.rows,
au.total_pages AS TotalPages
FROM
sys.partitions p
JOIN
sys.indexes i ON p.object_id = i.object_id AND p.index_id =
i.index_id
JOIN
sys.allocation_units au ON p.hobt_id = au.container_id
JOIN
sys.filegroups fg ON au.data_space_id = fg.data_space_id
WHERE
p.data_compression > 0
ORDER BY
OBJECT_NAME(i.object_id),
p.partition_number;
19. Check partition ranges and rows
SELECT
OBJECT_NAME(p.object_id) AS TableName,
i.name AS IndexName,
p.partition_number,
pr.value AS BoundaryValue,
p.rows AS [RowCount]
FROM
sys.partitions p
JOIN
sys.indexes i ON p.object_id = i.object_id AND p.index_id =
i.index_id
LEFT JOIN
sys.partition_range_values pr ON p.partition_number =
pr.boundary_id AND pr.function_id = i.data_space_id
WHERE
OBJECT_NAME(p.object_id) = 'YourTableName' -- Specify the
table name here
ORDER BY
p.partition_number;
Unlock peak efficiency with these 20 expert-level scripts, designed to expose hidden
issues, optimize resources, and keep your database running like a well-oiled machine.
Download this and similar documents from:
https://t.me/paragonacademy