0% found this document useful (0 votes)
2 views15 pages

SQL Server Tuning

This document provides a comprehensive guide of 20 SQL Server scripts aimed at performance tuning, addressing issues like long-running queries, index fragmentation, and deadlocks. Each script is designed to offer actionable solutions without unnecessary theory, making it a practical resource for database administrators. Users are advised to test the scripts in non-production environments before implementation.

Uploaded by

MOULOUD HAMIDAT
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views15 pages

SQL Server Tuning

This document provides a comprehensive guide of 20 SQL Server scripts aimed at performance tuning, addressing issues like long-running queries, index fragmentation, and deadlocks. Each script is designed to offer actionable solutions without unnecessary theory, making it a practical resource for database administrators. Users are advised to test the scripts in non-production environments before implementation.

Uploaded by

MOULOUD HAMIDAT
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 15

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

You might also like