Showing posts with label Performance Tuning. Show all posts
Showing posts with label Performance Tuning. Show all posts

Saturday, January 1, 2022

Query Plan read using T-SQL

How to Analyze SQL Server Execution Plans from a Saved XML File

If you have an execution plan saved as an XML file, here's how you can read and analyze it in SQL Server.

📄 Step 1: Load the Execution Plan File

Use the OPENROWSET function to load your .xml file into a variable.


DECLARE @plancontents VARCHAR(MAX), @xml XML;
SET @plancontents = (
    SELECT * 
    FROM OPENROWSET(BULK 'C:\TEMP\MyPlan.XML', SINGLE_CLOB) AS FileData
);

🔍 Step 2: Retrieve Recent Queries Matching a Specific Pattern

You can extract the top 100 recent SQL queries that match a specific text pattern, along with their execution times and plan handles.


SELECT TOP 100 
    execquery.last_execution_time AS [Date Time],
    execsql.TEXT AS [Script],
    execquery.plan_handle
FROM sys.dm_exec_query_stats AS execquery
CROSS APPLY sys.dm_exec_sql_text(execquery.sql_handle) AS execsql
WHERE execsql.TEXT LIKE '%WITH Temp_Accounts (%'
ORDER BY execquery.last_execution_time DESC;

🧠 Step 3: Analyze Execution Plan Nodes

Once you have the execution plan XML, you can use XQuery to dig into specific attributes of each node, such as cost estimates and operation types.


;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    c.value('.[1]/@PhysicalOp', 'nvarchar(max)') AS PhysicalOp,
    c.value('.[1]/@LogicalOp', 'nvarchar(max)') AS LogicalOp,
    c.value('.[1]/@AvgRowSize', 'nvarchar(max)') AS AvgRowSize,
    c.value('.[1]/@TableCardinality', 'nvarchar(max)') AS TableCardinality,
    c.value('.[1]/@Parallel', 'nvarchar(max)') AS Parallel,
    c.value('.[1]/@EstimateRebinds', 'nvarchar(max)') AS EstimateRebinds,
    c.value('.[1]/@EstimateRewinds', 'nvarchar(max)') AS EstimateRewinds,
    c.value('.[1]/@NodeId', 'nvarchar(max)') AS NodeId,
    c.value('.[1]/@EstimatedTotalSubtreeCost', 'nvarchar(max)') AS EstimatedTotalSubtreeCost,
    c.value('.[1]/@EstimateRows', 'nvarchar(max)') AS EstimateRows,
    c.value('.[1]/@EstimateIO', 'nvarchar(max)') AS EstimateIO,
    c.value('.[1]/@EstimateCPU', 'nvarchar(max)') AS EstimateCPU,
    c.query('.') AS ExecPlanNode
FROM @xml.nodes('//child::RelOp') AS T(c)
ORDER BY EstimatedTotalSubtreeCost DESC;

🔗 Useful References

💡 Final Thoughts

By leveraging SQL Server's XQuery capabilities and DMVs, you can gain deep insights into query performance and execution behavior directly from saved plan files. This method is particularly useful when troubleshooting or optimizing complex queries.

Sunday, December 26, 2021

CPU utilization trends

Track SQL Server CPU Usage for the Last 256 Minutes

Ever wonder how your SQL Server has been utilizing its CPU over time? This handy T-SQL script allows you to view the CPU utilization history for the last 256 minutes, broken down into one-minute intervals. It's a fantastic way to quickly pinpoint any performance bottlenecks or trends related to CPU usage.

The SQL Script

DECLARE @ts_now BIGINT = (
  SELECT cpu_ticks / (cpu_ticks / ms_ticks)
  FROM sys.dm_os_sys_info WITH (NOLOCK)
  );

SELECT TOP (256) SQLProcessUtilization AS [SQL Server Process CPU Utilization]
 ,SystemIdle AS [System Idle Process]
 ,100 - SystemIdle - SQLProcessUtilization AS [Other Process CPU Utilization]
 ,DATEADD(ms, - 1 * (@ts_now - [timestamp]), GETDATE()) AS [Event Time]
FROM (
 SELECT record.value('(./Record/@id)[1]', 'int') AS record_id
  ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS [SystemIdle]
  ,record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS [SQLProcessUtilization]
  ,[timestamp]
 FROM (
  SELECT [timestamp]
    ,CONVERT(XML, record) AS [record]
  FROM sys.dm_os_ring_buffers WITH (NOLOCK)
  WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
    AND record LIKE N'%<SystemHealth>%'
  ) AS x
 ) AS y
ORDER BY record_id DESC
OPTION (RECOMPILE);

How It Works

This script queries `sys.dm_os_ring_buffers`, specifically looking at the `RING_BUFFER_SCHEDULER_MONITOR` type. This ring buffer captures system health events, including CPU utilization. By parsing the XML output from the `record` column, we can extract details like `SystemIdle` (how much CPU is idle), `SQLProcessUtilization` (how much CPU SQL Server is using), and then calculate the CPU usage by other processes.

For more in-depth information about `sys.dm_os_ring_buffers` and how it's used, check out this great resource: Inside sys.dm_os_ring_buffers.

Give this script a try and let us know in the comments if you found it useful for your SQL Server monitoring!

Saturday, November 20, 2021

Determine which scalar UDFs are in-lineable

When you're working with SQL Server, understanding the properties of your functions can be really helpful for performance optimization. The following SQL query helps you identify your scalar user-defined functions (UDFs) and check if they are "inlineable."

Check if Your SQL Scalar Functions are Inlineable

This query provides insight into whether a scalar UDF can be inlined, which can significantly improve query performance by essentially expanding the function's logic directly into the calling query, avoiding the overhead of a function call.

SELECT OBJECT_NAME(m.object_id) AS [Function Name]
 ,is_inlineable
 ,inline_type
FROM sys.sql_modules AS m WITH (NOLOCK)
LEFT OUTER JOIN sys.dm_exec_function_stats AS efs WITH (NOLOCK) ON m.object_id = efs.object_id
WHERE efs.type_desc = N'SQL_SCALAR_FUNCTION'
OPTION (RECOMPILE);

For more detailed information on scalar UDF inlining and related system views, you can refer to the official Microsoft documentation:

Monday, October 25, 2021

How to Identify Tables Without a Primary Key in SQL Server

In SQL Server, primary keys play a crucial role in maintaining data integrity and optimizing query performance. However, during development or when working with legacy databases, you may come across tables that lack a primary key—either by design or oversight.

Identifying these tables is an essential step in ensuring your database is well-structured and reliable.

📌 SQL Query to Find Tables Without a Primary Key

The query below retrieves a list of all tables in your database that do not have a primary key defined:


SELECT s.[name] + N'.' + t.[name] AS [Table]
FROM sys.tables t
INNER JOIN sys.schemas s ON t.[schema_id] = s.[schema_id]
WHERE NOT EXISTS (
    SELECT 1
    FROM sys.key_constraints kc
    WHERE kc.[type] = N'PK'
      AND kc.parent_object_id = t.[object_id]
);

🧩 Why This Matters

Tables without primary keys can lead to:

  • Duplicate or inconsistent data
  • Poor query performance
  • Issues with joins and indexing strategies
  • Complications in replication, ETL, and other operations

Make it a habit to review your schema regularly and ensure every table has a well-defined primary key—unless there's a valid reason not to.

Wednesday, August 18, 2021

Get Volume statistics (Storage Partition)

Ever wonder about the nitty-gritty details of where your SQL Server databases and files live? There's a handy dynamic management function in SQL Server that spills the beans on the operating system volume (directory) where your data resides. It's incredibly useful for checking out the attributes of your physical disk drives or getting a quick peek at the available free space in a specific directory.

SELECT DISTINCT vs.volume_mount_point
 ,vs.file_system_type
 ,vs.logical_volume_name
 ,CONVERT(DECIMAL(18, 2), vs.total_bytes / 1073741824.0) AS [Total Size (GB)]
 ,CONVERT(DECIMAL(18, 2), vs.available_bytes / 1073741824.0) AS [Available Size (GB)]
 ,CONVERT(DECIMAL(18, 2), vs.available_bytes * 1. / vs.total_bytes * 100.) AS [Space Free %]
 ,vs.supports_compression
 ,vs.is_compressed
 ,vs.supports_sparse_files
 ,vs.supports_alternate_streams
FROM sys.master_files AS f WITH (NOLOCK)
CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.[file_id]) AS vs
ORDER BY vs.volume_mount_point
OPTION (RECOMPILE);

This SQL query leverages the sys.dm_os_volume_stats function to give you a comprehensive overview, including the mount point, file system type, logical volume name, total size, available space, and even the percentage of free space. It also provides information on whether the volume supports compression, is compressed, or supports sparse files and alternate streams.

For more in-depth documentation on this powerful function, you can refer to the official Microsoft documentation: sys.dm_os_volume_stats (Transact-SQL)

Saturday, June 19, 2021

Get in-memory OLTP index usage

Understanding how your SQL Server indexes are performing is crucial for database optimization. The following SQL query leverages sys.dm_db_xtp_index_stats to provide valuable insights into your in-memory OLTP (Hekaton) index usage. This dynamic management view gives you statistics like the number of scans, retries, rows touched, and rows returned for each index.

SELECT OBJECT_NAME(i.[object_id]) AS [Object Name],
       i.index_id,
       i.[name] AS [Index Name],
       i.[type_desc],
       xis.scans_started,
       xis.scans_retries,
       xis.rows_touched,
       xis.rows_returned
FROM sys.dm_db_xtp_index_stats AS xis WITH (NOLOCK)
INNER JOIN sys.indexes AS i WITH (NOLOCK) ON i.[object_id] = xis.[object_id]
                                          AND i.index_id = xis.index_id
ORDER BY OBJECT_NAME(i.[object_id])
OPTION (RECOMPILE);

This query helps you identify which indexes are being heavily utilized and which might be underperforming or not being used efficiently. For more detailed information on sys.dm_db_xtp_index_stats, you can refer to the official Microsoft documentation:

sys.dm_db_xtp_index_stats (Transact-SQL) - SQL Server | Microsoft Docs

You might also find these related articles helpful for further exploration of SQL Server memory management and performance:

Saturday, May 29, 2021

Check Memory pressure on the server

When working with SQL Server, monitoring memory is crucial for optimal performance. These queries provide valuable insights into your system's memory state, helping you identify and address potential memory pressure issues.

Checking for Process Memory Low Conditions

The following query helps determine if your SQL Server process is experiencing low physical or virtual memory. Ideally, both values should be zero.

SELECT m.process_physical_memory_low
 ,m.process_virtual_memory_low
FROM sys.dm_os_process_memory m --both should be zero

Current Memory Usage by SQL Server Process

This query displays the amount of physical memory currently being used by your SQL Server instance in megabytes (MB).

SELECT m.physical_memory_in_use_kb / 1024 [Mem Used in MB]
FROM sys.dm_os_process_memory m

Overall System Memory Status

Gain a comprehensive overview of your system's memory, including total physical memory, available physical memory, and the current system memory state. The `system_memory_state_desc` provides a quick indicator of memory health.

SELECT m.total_physical_memory_kb / 1024 [Mem in MB]
 ,m.available_physical_memory_kb / 1024 [Available Mem MB]
 ,m.system_memory_state_desc
FROM sys.dm_os_sys_memory m

Committed Memory Information

Understand how much memory has been committed by the operating system to SQL Server and the target memory for the instance.

SELECT m.committed_kb / 1024 [Committed in MB]
 ,m.committed_target_kb / 1024 [Target Mem MB]
FROM sys.dm_os_sys_info m

Possible System Memory State Descriptions:

  • Available physical memory is high: Your system has ample free physical memory.
  • Physical memory usage is steady: Memory usage is stable, with no significant fluctuations.
  • Available physical memory is low: Your system is running low on available physical memory.
  • Available physical memory is running low: A critical state indicating very little free physical memory.
  • Physical memory state is transitioning: The system's memory state is currently changing.

Detailed OS Memory Information

This query provides more detailed insights into your operating system's memory, including page file usage and system cache. Ideally, `System Memory State` should be "Available physical memory is high" for optimal performance.

SELECT total_physical_memory_kb/1024 AS [Total Memory (MB)],
       available_physical_memory_kb/1024 AS [Available Memory (MB)],
       total_page_file_kb/1024 AS [Total Page File (MB)],
       available_page_file_kb/1024 AS [Available Page File (MB)],
       system_cache_kb/1024 AS [System Cache (MB)],
       system_memory_state_desc AS [System Memory State] --should be "Available physical memory is high"
FROM sys.dm_os_sys_memory WITH (NOLOCK) OPTION (RECOMPILE);

Further Reading and Resources:

Saturday, May 1, 2021

Columnstore index physical statistics

Ever wondered how to peek behind the curtains of your SQL Server's columnstore indexes? This handy query will give you a detailed look at their physical stats, helping you understand their health and performance.

SELECT OBJECT_NAME(ps.object_id) AS [TableName]
 ,i.[name] AS [IndexName]
 ,ps.index_id
 ,ps.partition_number
 ,ps.delta_store_hobt_id
 ,ps.state_desc
 ,ps.total_rows
 ,ps.size_in_bytes
 ,ps.trim_reason_desc
 ,ps.generation
 ,ps.transition_to_compressed_state_desc
 ,ps.has_vertipaq_optimization
 ,ps.deleted_rows
 ,100 * (ISNULL(ps.deleted_rows, 0)) / ps.total_rows AS [Fragmentation]
FROM sys.dm_db_column_store_row_group_physical_stats AS ps WITH (NOLOCK)
INNER JOIN sys.indexes AS i WITH (NOLOCK) ON ps.object_id = i.object_id
 AND ps.index_id = i.index_id
ORDER BY ps.object_id
 ,ps.partition_number
 ,ps.row_group_id
OPTION (RECOMPILE);

This SQL script dives into sys.dm_db_column_store_row_group_physical_stats to provide insights like table and index names, row counts, size in bytes, and even a fragmentation percentage. It's a great tool for monitoring your columnstore indexes and ensuring they're performing optimally.

Thursday, April 29, 2021

Database performance information

Understanding Your SQL Server Databases: A Comprehensive Query

This post provides a powerful SQL query to gather crucial information about all databases on your SQL Server instance. You'll get insights into their **recovery model**, **log reuse wait description**, **log file size**, **log usage size**, and **compatibility level**, along with many other important database settings. This script is invaluable for monitoring database health and performance.


SELECT db.[name] AS [Database Name]
 ,SUSER_SNAME(db.owner_sid) AS [Database Owner]
 ,db.recovery_model_desc AS [Recovery Model]
 ,db.state_desc
 ,db.containment_desc
 ,db.log_reuse_wait_desc AS [Log Reuse Wait Description]
 ,CONVERT(DECIMAL(18, 2), ls.cntr_value / 1024.0) AS [Log Size (MB)]
 ,CONVERT(DECIMAL(18, 2), lu.cntr_value / 1024.0) AS [Log Used (MB)]
 ,CAST(CAST(lu.cntr_value AS FLOAT) / CAST(ls.cntr_value AS FLOAT) AS DECIMAL(18, 2)) * 100 AS [Log Used %]
 ,db.[compatibility_level] AS [DB Compatibility Level]
 ,db.is_mixed_page_allocation_on
 ,db.page_verify_option_desc AS [Page Verify Option]
 ,db.is_auto_create_stats_on
 ,db.is_auto_update_stats_on
 ,db.is_auto_update_stats_async_on
 ,db.is_parameterization_forced
 ,db.snapshot_isolation_state_desc
 ,db.is_read_committed_snapshot_on
 ,db.is_auto_close_on
 ,db.is_auto_shrink_on
 ,db.target_recovery_time_in_seconds
 ,db.is_cdc_enabled
 ,db.is_published
 ,db.is_distributor
 ,db.group_database_id
 ,db.replica_id
 ,db.is_memory_optimized_elevate_to_snapshot_on
 ,db.delayed_durability_desc
 ,db.is_auto_create_stats_incremental_on
 ,db.is_query_store_on
 ,db.is_sync_with_backup
 ,db.is_temporal_history_retention_enabled
 ,db.is_supplemental_logging_enabled
 ,db.is_remote_data_archive_enabled
 ,db.is_encrypted
 ,de.encryption_state
 ,de.percent_complete
 ,de.key_algorithm
 ,de.key_length
 ,db.resource_pool_id
 ,db.is_tempdb_spill_to_remote_store
 ,db.is_result_set_caching_on
 ,db.is_accelerated_database_recovery_on
 ,is_stale_page_detection_on
FROM sys.databases AS db WITH (NOLOCK)
INNER JOIN sys.dm_os_performance_counters AS lu WITH (NOLOCK) ON db.name = lu.instance_name
INNER JOIN sys.dm_os_performance_counters AS ls WITH (NOLOCK) ON db.name = ls.instance_name
LEFT OUTER JOIN sys.dm_database_encryption_keys AS de WITH (NOLOCK) ON db.database_id = de.database_id
WHERE lu.counter_name LIKE N'Log File(s) Used Size (KB)%'
 AND ls.counter_name LIKE N'Log File(s) Size (KB)%'
 AND ls.cntr_value > 0
ORDER BY db.[name]
OPTION (RECOMPILE);

This query leverages two key Dynamic Management Views (DMVs) for its data:


Monday, March 29, 2021

Get IO Statistics by database

Understanding and monitoring database I/O is crucial for maintaining a healthy and performant SQL Server. High I/O can often indicate bottlenecks and impact overall system responsiveness. The following SQL query provides a quick and effective way to analyze I/O statistics across your databases, helping you identify which databases are generating the most read and write activity.

WITH IOStat
AS (
 SELECT DB_NAME(database_id) AS [Database Name]
  ,CAST(SUM(num_of_bytes_read + num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS [TotalMB]
  ,CAST(SUM(num_of_bytes_read) / 1048576 AS DECIMAL(12, 2)) AS [ReadMB]
  ,CAST(SUM(num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS [WriteMB]
 FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS [DM_IO_STATS]
 GROUP BY database_id
 )
SELECT ROW_NUMBER() OVER (
  ORDER BY TotalMB DESC
  ) AS [I/O Rank]
 ,[Database Name]
 ,TotalMB AS [I/O Total (MB)]
 ,CAST(TotalMB / SUM(TotalMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [I/O Total %]
 ,ReadMB AS [I/O Read (MB)]
 ,CAST(ReadMB / SUM(ReadMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [I/O Read %]
 ,WriteMB AS [I/O Write (MB)]
 ,CAST(WriteMB / SUM(WriteMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [I/O Write %]
FROM IOStat
ORDER BY [I/O Rank]
OPTION (RECOMPILE);

This query leverages the **sys.dm_io_virtual_file_stats** Dynamic Management View (DMV), which provides detailed I/O statistics for data and log files. By grouping the results by database, we can easily see which databases are contributing most to the overall I/O workload on your SQL Server instance.

For more in-depth information on the **sys.dm_io_virtual_file_stats** DMV, you can refer to the official Microsoft documentation: sys.dm_io_virtual_file_stats.

Sunday, February 28, 2021

Get CPU utilization by database

Ever wonder which database on your SQL Server instance is consuming the most CPU power? This handy SQL query helps you pinpoint those resource-intensive databases.

WITH DB_CPU
AS (
 SELECT pa.DatabaseID
  ,DB_Name(pa.DatabaseID) AS [Database Name]
  ,SUM(qs.total_worker_time / 1000) AS [CPU_Time_Ms]
 FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK)
 CROSS APPLY (
  SELECT CONVERT(INT, value) AS [DatabaseID]
  FROM sys.dm_exec_plan_attributes(qs.plan_handle)
  WHERE attribute = N'dbid'
  ) AS pa
 GROUP BY DatabaseID
 )
SELECT ROW_NUMBER() OVER (
  ORDER BY [CPU_Time_Ms] DESC
  ) AS [CPU Rank]
 ,[Database Name]
 ,[CPU_Time_Ms] AS [CPU Time (ms)]
 ,CAST([CPU_Time_Ms] * 1.0 / SUM([CPU_Time_Ms]) OVER () * 100.0 AS DECIMAL(5, 2)) AS [CPU Percent]
FROM DB_CPU
WHERE DatabaseID <> 32767 -- ResourceDB
ORDER BY [CPU Rank]
OPTION (RECOMPILE);

Keep in mind that the results of this query depend on the currently cached query plans.

For more in-depth information on the Dynamic Management Views (DMVs) used in this query, check out the official Microsoft documentation:

Tuesday, December 29, 2020

Get top average elapsed time queries

Ever wondered which queries are hogging resources in your SQL Server database? This handy SQL script can help you identify those troublesome queries by showing you the top 50 based on average elapsed time. It's a fantastic way to quickly pinpoint performance bottlenecks!

SELECT TOP (50) DB_NAME(t.[dbid]) AS [Database Name]
 ,REPLACE(REPLACE(LEFT(t.[text], 255), CHAR(10), ''), CHAR(13), '') AS [Short Query Text]
 ,qs.total_elapsed_time / qs.execution_count AS [Avg Elapsed Time]
 ,qs.min_elapsed_time
 ,qs.max_elapsed_time
 ,qs.last_elapsed_time
 ,qs.execution_count AS [Execution Count]
 ,qs.total_logical_reads / qs.execution_count AS [Avg Logical Reads]
 ,qs.total_physical_reads / qs.execution_count AS [Avg Physical Reads]
 ,qs.total_worker_time / qs.execution_count AS [Avg Worker Time]
 ,CASE WHEN CONVERT(NVARCHAR(max), qp.query_plan) COLLATE Latin1_General_BIN2 LIKE N'%<MissingIndexes>%' THEN 1 ELSE 0 END AS [Has Missing Index]
 ,qs.creation_time AS [Creation Time]
--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel
FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK)
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t
CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp
ORDER BY qs.total_elapsed_time / qs.execution_count DESC
OPTION (RECOMPILE);

This script leverages two powerful SQL Server Dynamic Management Views (DMVs):

  • sys.dm_exec_query_plan: This DMV provides the execution plan for a query, which is crucial for understanding how SQL Server intends to run your queries and identifying potential issues.
  • sys.dm_exec_query_stats: This DMV returns aggregated performance statistics for cached query plans. It's your go-to for seeing how queries have performed over time.

By combining data from these two DMVs, the script gives you a comprehensive overview of your top-performing (or rather, top-resource-consuming) queries, including metrics like average elapsed time, logical and physical reads, and even whether a missing index is suggested in the query plan. This can be an invaluable tool for any database administrator or developer looking to optimize SQL Server performance!

Sunday, November 15, 2020

Get the most frequently executed queries for this database

Uncovering Your Most Frequent and Resource-Intensive SQL Queries

Ever wonder which queries are truly hammering your SQL Server? Or which ones are eating up the most resources? This handy T-SQL script helps you pinpoint the top 50 queries based on their execution count, along with crucial performance metrics like logical reads and worker time.

SQL Script to Analyze Query Performance

Here's the SQL query that will give you insights into your database's performance:

SELECT TOP (50) LEFT(t.[text], 50) AS [Short Query Text]
 ,qs.execution_count AS [Execution Count]
 ,qs.total_logical_reads AS [Total Logical Reads]
 ,qs.total_logical_reads / qs.execution_count AS [Avg Logical Reads]
 ,qs.total_worker_time AS [Total Worker Time]
 ,qs.total_worker_time / qs.execution_count AS [Avg Worker Time]
 ,qs.total_elapsed_time AS [Total Elapsed Time]
 ,qs.total_elapsed_time / qs.execution_count AS [Avg Elapsed Time]
 ,CASE WHEN CONVERT(NVARCHAR(max), qp.query_plan) COLLATE Latin1_General_BIN2 LIKE N'%<MissingIndexes>%' THEN 1 ELSE 0 END AS [Has Missing Index]
 ,qs.creation_time AS [Creation Time]
--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel
FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK)
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t
CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp
WHERE t.dbid = DB_ID()
ORDER BY qs.execution_count DESC
OPTION (RECOMPILE);

Understanding the Metrics

This script leverages two powerful SQL Server Dynamic Management Views (DMVs):

By analyzing the output, you can quickly identify queries that are frequently executed, have high logical reads (indicating significant I/O operations), or consume a lot of worker time (CPU usage). The "Has Missing Index" column is particularly useful for spotting potential performance bottlenecks that could be resolved with a new index.

Saturday, November 7, 2020

How to read the SQL Server query plan file using TSQL

 How to read the SQL Server query plan file using T-SQL

In this post, I'll explain how to read an SQL Server execution query plan from the SSMS query file.

DECLARE @plancontents xml, @xml XML
SET @plancontents = (SELECT * FROM OPENROWSET(BULK 'P:\\Users\\hpatterson\\Documents\\myplan.sqlplan', SINGLE_CLOB) x)
-- Remove unnecessary namespace
-- SET @plancontents = REPLACE(@plancontents, 'xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan"', '')
-- Store the cleaned content into the XML variable
SET @xml = CAST(@plancontents AS XML)

-- You can also get the query plan directly like this:
-- SELECT @xml = query_plan FROM sys.dm_exec_query_plan
SELECT @xml

-- Notice the removal of the xmlns=http://schemas.microsoft.com/sqlserver/2004/07/showplan attribute

-- Now, let's process all the execution plan nodes and extract relevant details
WITH xmlnamespaces (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    c.value('.[1]/@PhysicalOp', 'nvarchar(max)') AS PhysicalOp,
    c.value('.[1]/@LogicalOp', 'nvarchar(max)') AS LogicalOp,
    c.value('.[1]/@AvgRowSize', 'nvarchar(max)') AS AvgRowSize,
    c.value('.[1]/@TableCardinality', 'nvarchar(max)') AS TableCardinality,
    c.value('.[1]/@Parallel', 'nvarchar(max)') AS Parallel,
    c.value('.[1]/@EstimateRebinds', 'nvarchar(max)') AS EstimateRebinds,
    c.value('.[1]/@EstimateRewinds', 'nvarchar(max)') AS EstimateRewinds,
    c.value('.[1]/@NodeId', 'nvarchar(max)') AS NodeId,
    c.value('.[1]/@EstimatedTotalSubtreeCost', 'nvarchar(max)') AS EstimatedTotalSubtreeCost,
    c.value('.[1]/@EstimateRows', 'nvarchar(max)') AS EstimateRows,
    c.value('.[1]/@EstimateIO', 'nvarchar(max)') AS EstimateIO,
    c.value('.[1]/@EstimateCPU', 'nvarchar(max)') AS EstimateCPU,
    -- This will return the node XML for easier inspection
    c.query('.') AS ExecPlanNode        
FROM 
    -- Only returns nodes with the name RelOp, including those that are nested
    @xml.nodes('//child::RelOp') T(c)
ORDER BY EstimatedTotalSubtreeCost DESC

Sunday, November 1, 2020

How to find a backup task by percentage complete


How to find a backup task by percentage complete

In this post, I will demonstrate how to track the completion of a backup task using a percentage indicator.


SELECT query = a.text, start_time, percent_complete,
    eta = dateadd(second, estimated_completion_time/1000, getdate())
FROM sys.dm_exec_requests r
    CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) a
WHERE r.command LIKE 'BACKUP%'

For more related information, check out the following resources:

Find Databases Without Backup
Backup History Using T-SQL
Restore Database

Saturday, August 29, 2020

Get top total worker time queries

 Get top total worker time queries

Ever wondered which SQL queries are hogging resources on your database? This handy SQL script helps you identify the top 50 culprits based on their total worker time. It provides a quick overview of query performance, including average execution times, logical reads, and even flags queries that might benefit from a missing index!

SELECT TOP (50) DB_NAME(t.[dbid]) AS [Database Name]
 ,REPLACE(REPLACE(LEFT(t.[text], 255), CHAR(10), ''), CHAR(13), '') AS [Short Query Text]
 ,qs.total_worker_time AS [Total Worker Time]
 ,qs.min_worker_time AS [Min Worker Time]
 ,qs.total_worker_time / qs.execution_count AS [Avg Worker Time]
 ,qs.max_worker_time AS [Max Worker Time]
 ,qs.min_elapsed_time AS [Min Elapsed Time]
 ,qs.total_elapsed_time / qs.execution_count AS [Avg Elapsed Time]
 ,qs.max_elapsed_time AS [Max Elapsed Time]
 ,qs.min_logical_reads AS [Min Logical Reads]
 ,qs.total_logical_reads / qs.execution_count AS [Avg Logical Reads]
 ,qs.max_logical_reads AS [Max Logical Reads]
 ,qs.execution_count AS [Execution Count]
 ,CASE WHEN CONVERT(NVARCHAR(max), qp.query_plan) COLLATE Latin1_General_BIN2 LIKE N'%<MissingIndexes>%' THEN 1 ELSE 0 END AS [Has Missing Index]
 ,qs.creation_time AS [Creation Time]
--,t.[text] AS [Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel
FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK)
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t
CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp
ORDER BY qs.total_worker_time DESC
OPTION (RECOMPILE);

This script leverages two powerful SQL Server Dynamic Management Views (DMVs) for its insights:

Give this script a try and see what insights you can uncover about your database's performance!

Saturday, July 25, 2020

Query statistics for a single table

SQL Server Statistics Overview

sys.stats: This contains a row for each statistics object related to tables, indexes, and indexed views in a SQL Server database.

sys.dm_db_stats_properties: This returns details about statistics for a given database object (either a table or an indexed view) in the current SQL Server database.

Query to Check Statistics Information

Use the following query to retrieve statistics details for a specific table or indexed view:

-- Execute the query below to inspect statistics
SELECT sp.stats_id, 
       name, 
       filter_definition, 
       last_updated, 
       rows, 
       rows_sampled, 
       steps, 
       unfiltered_rows, 
       modification_counter
FROM sys.stats AS stat
     CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp
WHERE stat.object_id = OBJECT_ID('HumanResources.Employee');
    

Explanation of Columns:

  • Stats_ID: The unique ID for the statistics object.
  • Name: The name of the statistics object.
  • Last_updated: The timestamp of when the statistics were last updated.
  • Rows: The total number of rows at the time of the last update.
  • Rows_sampled: The number of rows sampled for generating the statistics.
  • Unfiltered_rows: The count of rows in the table without any filters (it may be the same as rows_sampled if no filter is applied).
  • Modification_counter: This counts the number of changes made to the table since the last update to the statistics.
Table Statistics

Identify Auto-Created Statistics by SQL Server

To find statistics that were automatically generated by SQL Server, run this query:

-- Query to find auto-created statistics by SQL Server
SELECT sp.stats_id, 
       name, 
       filter_definition, 
       last_updated, 
       rows, 
       rows_sampled, 
       steps, 
       unfiltered_rows, 
       modification_counter
FROM sys.stats AS stat
     CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp
WHERE stat.object_id = OBJECT_ID('HumanResources.Employee')
AND name LIKE '_WA%';
    

Helpful Link

For more information on the sys.dm_db_stats_properties dynamic management view, check the official documentation:

sys.dm_db_stats_properties - Microsoft Docs

Example: Identify Tables with Modified Statistics

The query below retrieves a list of all tables, indexed views, and statistics in the current database where the leading column has been modified more than 1000 times since the last statistics update:

SELECT obj.name, obj.object_id, stat.name, stat.stats_id, last_updated, modification_counter  
FROM sys.objects AS obj   
INNER JOIN sys.stats AS stat ON stat.object_id = obj.object_id  
CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp  
WHERE modification_counter > 1000;
    

IO Statistics in SQL Server using Query

TSQL Latency and I/O Statistics Queries

1. Average Latency

To calculate the average read and write latency, you can use the following SQL query. It breaks down the latency by reads, writes, and transfers:

    -- Calculate Avg Latency
    SELECT 
        [ReadLatency] = CASE WHEN [num_of_reads] = 0 THEN 0 ELSE ([io_stall_read_ms] / [num_of_reads]) END,
        [WriteLatency] = CASE WHEN [num_of_writes] = 0 THEN 0 ELSE ([io_stall_write_ms] / [num_of_writes]) END,
        [Latency] = CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0) THEN 0 ELSE ([io_stall] / ([num_of_reads] + [num_of_writes])) END,
        [AvgBPerRead] = CASE WHEN [num_of_reads] = 0 THEN 0 ELSE ([num_of_bytes_read] / [num_of_reads]) END,
        [AvgBPerWrite] = CASE WHEN [num_of_writes] = 0 THEN 0 ELSE ([num_of_bytes_written] / [num_of_writes]) END,
        [AvgBPerTransfer] = CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0) THEN 0 ELSE
            (([num_of_bytes_read] + [num_of_bytes_written]) / ([num_of_reads] + [num_of_writes])) END,
        LEFT([mf].[physical_name], 2) AS [Drive], 
        DB_NAME([vfs].[database_id]) AS [DB], 
        [mf].[physical_name]
    FROM 
        sys.dm_io_virtual_file_stats(NULL, NULL) AS [vfs]
    JOIN 
        sys.master_files AS [mf] ON [vfs].[database_id] = [mf].[database_id] AND [vfs].[file_id] = [mf].[file_id]
    ORDER BY 
        [WriteLatency] DESC;
    
Avg Latency

2. I/O Statistics by File for Current Database

This query helps you track I/O statistics by file for the current database, including read and write percentages and I/O stall breakdowns.

    -- Check I/O Statistics by file for the current database
    SELECT 
        DB_NAME(DB_ID()) AS [DB_Name], 
        DFS.name AS [Logical_Name], 
        DIVFS.[file_id], 
        DFS.physical_name AS [PH_Name], 
        DIVFS.num_of_reads, 
        DIVFS.num_of_writes, 
        DIVFS.io_stall_read_ms, 
        DIVFS.io_stall_write_ms,
        CAST(100. * DIVFS.io_stall_read_ms / (DIVFS.io_stall_read_ms + DIVFS.io_stall_write_ms) AS DECIMAL(10, 1)) AS [IO_Stall_Reads_Pct],
        CAST(100. * DIVFS.io_stall_write_ms / (DIVFS.io_stall_write_ms + DIVFS.io_stall_read_ms) AS DECIMAL(10, 1)) AS [IO_Stall_Writes_Pct],
        (DIVFS.num_of_reads + DIVFS.num_of_writes) AS [Writes + Reads], 
        CAST(DIVFS.num_of_bytes_read / 1048576.0 AS DECIMAL(10, 2)) AS [MB Read], 
        CAST(DIVFS.num_of_bytes_written / 1048576.0 AS DECIMAL(10, 2)) AS [MB Written],
        CAST(100. * DIVFS.num_of_reads / (DIVFS.num_of_reads + DIVFS.num_of_writes) AS DECIMAL(10, 1)) AS [# Reads Pct],
        CAST(100. * DIVFS.num_of_writes / (DIVFS.num_of_reads + DIVFS.num_of_writes) AS DECIMAL(10, 1)) AS [# Write Pct],
        CAST(100. * DIVFS.num_of_bytes_read / (DIVFS.num_of_bytes_read + DIVFS.num_of_bytes_written) AS DECIMAL(10, 1)) AS [Read Bytes Pct],
        CAST(100. * DIVFS.num_of_bytes_written / (DIVFS.num_of_bytes_read + DIVFS.num_of_bytes_written) AS DECIMAL(10, 1)) AS [Written Bytes Pct]
    FROM 
        sys.dm_io_virtual_file_stats(DB_ID(), NULL) AS DIVFS
    INNER JOIN 
        sys.database_files AS DFS WITH (NOLOCK) ON DIVFS.[file_id] = DFS.[file_id];
    
I/O Statistics by File

3. I/O Utilization by Database

The following query shows the I/O statistics for each database in your system, helping you determine the total I/O in megabytes and its percentage of the system's total I/O.

    -- Check I/O utilization by database
    WITH AggregateIOStatistics AS
    (
        SELECT 
            DB_NAME(database_id) AS [DB Name],
            CAST(SUM(num_of_bytes_read + num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS io_in_mb
        FROM 
            sys.dm_io_virtual_file_stats(NULL, NULL) AS [DM_IO_STATS]
        GROUP BY 
            database_id
    )
    SELECT 
        ROW_NUMBER() OVER (ORDER BY io_in_mb DESC) AS [I/O Rank], 
        [DB Name], 
        io_in_mb AS [Total I/O (MB)],
        CAST(io_in_mb / SUM(io_in_mb) OVER() * 100.0 AS DECIMAL(5, 2)) AS [I/O Percent]
    FROM 
        AggregateIOStatistics
    ORDER BY 
        [I/O Rank];
    
I/O Statistics by DB

4. Drive Level Latency Analysis

Use this query to check the latency per disk drive. The levels of latency are categorized as follows:

  • Brilliant: < 1ms
  • Great: < 5ms
  • Good Quality: 5 – 10ms
  • Poor: 10 – 20ms
  • Horrific: 20 – 100ms
  • Disgracefully Bad: 100 – 500ms
  • WOW!: > 500ms
    -- Check Drive level latency
    SELECT 
        DISK_Drive,
        CASE WHEN DISK_num_of_reads = 0 THEN 0 ELSE (DISK_io_stall_read_ms / DISK_num_of_reads) END AS [Read Latency],
        CASE WHEN DISK_io_stall_write_ms = 0 THEN 0 ELSE (DISK_io_stall_write_ms / DISK_num_of_writes) END AS [Write Latency],
        CASE WHEN (DISK_num_of_reads = 0 AND DISK_num_of_writes = 0) THEN 0 ELSE (DISK_io_stall / (DISK_num_of_reads + DISK_num_of_writes)) END AS [Overall Latency],
        CASE WHEN DISK_num_of_reads = 0 THEN 0 ELSE (DISK_num_of_bytes_read / DISK_num_of_reads) END AS [Avg Bytes/Read],
        CASE WHEN DISK_io_stall_write_ms = 0 THEN 0 ELSE (DISK_num_of_bytes_written / DISK_num_of_writes) END AS [Avg Bytes/Write],
        CASE WHEN (DISK_num_of_reads = 0 AND DISK_num_of_writes = 0) THEN 0 ELSE ((DISK_num_of_bytes_read + DISK_num_of_bytes_written) / (DISK_num_of_reads + DISK_num_of_writes)) END AS [Avg Bytes/Transfer]
    FROM 
        (SELECT 
            LEFT(UPPER(mf.physical_name), 2) AS DISK_Drive, 
            SUM(num_of_reads) AS DISK_num_of_reads,
            SUM(io_stall_read_ms) AS DISK_io_st

Saturday, July 11, 2020

How to read Deadlock from system health of SQL server

 How to read the Deadlock from the system health of the SQL server

This query can be used to analyze deadlock events from the system health extended events in SQL Server.

SELECT 
    xed.value('@timestamp', 'datetime') AS Creation_Date,
    xed.query('.') AS Extended_Event,
    xed.value('(//deadlock/process-list/process[1]/@clientapp)[1]', 'NVarChar(50)') AS VictimClientApp,
    xed.value('(//deadlock/process-list/process[1]/@spid)[1]', 'NVarChar(15)') AS VictimProcessID,
    CAST(REPLACE(xed.value('(//deadlock/process-list/process[1]/@lastbatchstarted)[1]', 'NChar(23)'), N'T', N' ') AS DATETIME) AS VictimLastBatchStarted,
    xed.value('(//deadlock/process-list/process[1]/@lockMode)[1]', 'NVarChar(15)') AS VictimLockMode,
    xed.value('(//deadlock/process-list/process[1]/@xactid)[1]', 'NVarChar(15)') AS VictimXActID,
    xed.value('(//deadlock/process-list/process[1]/@clientapp)[1]', 'NVarChar(50)') AS VictimClientApp,
    -- Live Process
    xed.value('(//deadlock/process-list/process[2]/@spid)[1]', 'NVarChar(15)') AS LiveProcessID,
    CAST(REPLACE(xed.value('(//deadlock/process-list/process[2]/@lastbatchstarted)[1]', 'NChar(23)'), N'T', N' ') AS DATETIME) AS LiveLastBatchStarted,
    xed.value('(//deadlock/process-list/process[2]/@lockMode)[1]', 'NVarChar(15)') AS LiveLockMode,
    xed.value('(//deadlock/process-list/process[2]/@xactid)[1]', 'NVarChar(15)') AS LiveXActID,
    xed.value('(//deadlock/process-list/process[2]/@clientapp)[1]', 'NVarChar(50)') AS LiveClientApp,
    -- Live Resource
    xed.value('(//deadlock/resource-list/pagelock[1]/@fileid)[1]', 'NVarChar(15)') AS LiveFileID,
    xed.value('(//deadlock/resource-list/pagelock[1]/@pageid)[1]', 'NVarChar(15)') AS LivePageID,
    xed.value('(//deadlock/resource-list/pagelock[1]/@objectname)[1]', 'NVarChar(50)') AS LiveObjectName,
    xed.value('(//deadlock/resource-list/pagelock[1]/@mode)[1]', 'NVarChar(50)') AS LiveLockModeHeld,
    xed.value('(//deadlock/resource-list/pagelock[1]/waiter-list/waiter/@mode)[1]', 'NVarChar(50)') AS VictimLockModeRequest,
    -- Victim Resource
    xed.value('(//deadlock/resource-list/pagelock[2]/@fileid)[1]', 'NVarChar(15)') AS VictimFileID,
    xed.value('(//deadlock/resource-list/pagelock[2]/@pageid)[1]', 'NVarChar(15)') AS VictimPageID,
    xed.value('(//deadlock/resource-list/pagelock[2]/@objectname)[1]', 'NVarChar(50)') AS VictimObjectName,
    xed.value('(//deadlock/resource-list/pagelock[2]/@mode)[1]', 'NVarChar(50)') AS VictimLockModeHeld,
    xed.value('(//deadlock/resource-list/pagelock[2]/waiter-list/waiter/@mode)[1]', 'NVarChar(50)') AS LiveLockModeRequest,
    -- Input Buffers
    xed.value('(//deadlock/process-list/process[1]/executionStack/frame/@procname)[1]', 'NVarChar(100)') AS VictimProcName,
    xed.value('(//deadlock/process-list/process[1]/executionStack/frame)[1]', 'VarChar(max)') AS VictimExecStack,
    xed.value('(//deadlock/process-list/process[2]/executionStack/frame/@procname)[1]', 'NVarChar(max)') AS LiveProcName,
    xed.value('(//deadlock/process-list/process[2]/executionStack/frame)[1]', 'VarChar(max)') AS LiveExecStack,
    RTRIM(LTRIM(REPLACE(xed.value('(//deadlock/process-list/process[1]/inputbuf)[1]', 'NVarChar(2048)'), NCHAR(10), N''))) AS VictimInputBuffer,
    RTRIM(LTRIM(REPLACE(xed.value('(//deadlock/process-list/process[2]/inputbuf)[1]', 'NVARCHAR(2048)'), NChar(10), N''))) AS LiveInputBuffer
FROM 
    (SELECT CAST([target_data] AS XML) AS Target_Data
     FROM sys.dm_xe_session_targets AS xt
     INNER JOIN sys.dm_xe_sessions AS xs ON xs.address = xt.event_session_address
     WHERE xs.name = N'system_health'
     AND xt.target_name = N'ring_buffer') AS XML_Data
CROSS APPLY Target_Data.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData(xed)
ORDER BY Creation_Date DESC;

Wednesday, April 29, 2020

Find a current blocking connection

 Find a current blocking connection

Here's a handy SQL query to help you identify and troubleshoot blocking in your SQL Server environment. This script combines information from several dynamic management views (DMVs) to give you a clear picture of what's waiting, what's blocking it, and the SQL statements involved.

SELECT t1.resource_type AS [Lock Type],
  DB_NAME(resource_database_id) AS [Database],
  t1.resource_associated_entity_id AS [Blocking Object],
  t1.request_mode AS [Lock Request], -- The lock being requested
  t1.request_session_id AS [Waiter SPID],
  t2.wait_duration_ms AS [Wait Time], -- How long the session has been waiting
  (
    SELECT [text]
    FROM sys.dm_exec_requests AS r WITH (NOLOCK) -- Get the SQL for the waiting session's batch
    CROSS APPLY sys.dm_exec_sql_text(r.[sql_handle])
    WHERE r.session_id = t1.request_session_id
  ) AS [Waiter Batch],
  (
    SELECT SUBSTRING(qt.[text], r.statement_start_offset / 2, (
      CASE
        WHEN r.statement_end_offset = - 1
        THEN LEN(CONVERT(NVARCHAR(max), qt.[text])) * 2
        ELSE r.statement_end_offset
      END - r.statement_start_offset
    ) / 2)
    FROM sys.dm_exec_requests AS r WITH (NOLOCK)
    CROSS APPLY sys.dm_exec_sql_text(r.[sql_handle]) AS qt
    WHERE r.session_id = t1.request_session_id
  ) AS [Waiter Statement], -- The specific statement being blocked
  t2.blocking_session_id AS [Blocker SPID], -- The SPID of the session causing the block
  (
    SELECT [text]
    FROM sys.sysprocesses AS p -- Get the SQL for the blocking session's batch
    CROSS APPLY sys.dm_exec_sql_text(p.[sql_handle])
    WHERE p.spid = t2.blocking_session_id
  ) AS [Blocker Batch]
FROM sys.dm_tran_locks AS t1 WITH (NOLOCK)
INNER JOIN sys.dm_os_waiting_tasks AS t2 WITH (NOLOCK) ON t1.lock_owner_address = t2.resource_address
OPTION (RECOMPILE);

This query joins sys.dm_tran_locks (information about current lock requests) with sys.dm_os_waiting_tasks (information about tasks that are waiting on something) to link waiting sessions to their blockers. It then uses sys.dm_exec_requests and sys.sysprocesses to retrieve the actual SQL text for both the waiting and blocking sessions, helping you pinpoint the exact statements causing issues.

For more detailed information on the DMVs used in this query, you can refer to the official Microsoft documentation:

Popular Posts