Skip to main content

sqlserver-monitoring

SQL Server monitoring — SQL Agent jobs, error log, database health, blocking chains, deadlock analysis, long-running transactions, and disk space.

Source facts

Repository
vince-winkintel/sql-server-skills
Last source activity
October 30, 2026 at 19:28
Detected SKILL.md language
English
Stars
2
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
sqlserver-monitoring
description
SQL Server monitoring — SQL Agent jobs, error log, database health, blocking chains, deadlock analysis, long-running transactions, and disk space.
# SQL Server Monitoring Use this skill to check SQL Server health, investigate blocking or deadlocks, monitor SQL Agent jobs, and review the error log. --- ## 1. SQL Agent Jobs ### Check Job Status ```sql -- All jobs and their last run result SELECT j.name AS job_name, j.enabled, CASE jh.run_status WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' WHEN 2 THEN 'Retry' WHEN 3 THEN 'Cancelled' WHEN 4 THEN 'In Progress' END AS last_run_status, msdb.dbo.agent_datetime(jh.run_date, jh.run_time) AS last_run_time, -- Duration in HH:MM:SS RIGHT('0' + CAST(jh.run_duration / 10000 AS VARCHAR), 2) + ':' + RIGHT('0' + CAST((jh.run_duration % 10000) / 100 AS VARCHAR), 2) + ':' + RIGHT('0' + CAST(jh.run_duration % 100 AS VARCHAR), 2) AS duration_hhmmss, jh.message FROM msdb.dbo.sysjobs j LEFT JOIN msdb.dbo.sysjobhistory jh ON j.job_id = jh.job_id AND jh.instance_id = ( SELECT MAX(instance_id) FROM msdb.dbo.sysjobhistory WHERE job_id = j.job_id AND step_id = 0 ) ORDER BY last_run_time DESC; ``` ### Find Failed Jobs in Last 24 Hours ```sql SELECT j.name AS job_name, msdb.dbo.agent_datetime(jh.run_date, jh.run_time) AS run_time, jh.step_id, jh.step_name, jh.message FROM msdb.dbo.sysjobs j JOIN msdb.dbo.sysjobhistory jh ON j.job_id = jh.job_id WHERE jh.run_status = 0 -- 0 = Failed AND msdb.dbo.agent_datetime(jh.run_date, jh.run_time) >= DATEADD(HOUR, -24, GETDATE()) ORDER BY run_time DESC; ``` ### Currently Running Jobs ```sql SELECT j.name AS job_name, ja.start_execution_date, DATEDIFF(MINUTE, ja.start_execution_date, GETDATE()) AS running_minutes, ja.last_executed_step_id, ja.last_executed_step_date FROM msdb.dbo.sysjobactivity ja JOIN msdb.dbo.sysjobs j ON ja.job_id = j.job_id WHERE ja.session_id = (SELECT MAX(session_id) FROM msdb.dbo.syssessions) AND ja.start_execution_date IS NOT NULL AND ja.stop_execution_date IS NULL; ``` --- ## 2. Error Log ```sql -- Read current SQL Server error log -- Parameters: (log file #, log type, search str1, search str2, start, end, sort order) EXEC xp_readerrorlog 0, 1, NULL, NULL, NULL, NULL, 'DESC'; -- Filter to errors only (severity 17+) EXEC xp_readerrorlog 0, 1, 'Error', NULL, NULL, NULL, 'DESC'; -- Check for specific timeframe DECLARE @start DATETIME = DATEADD(HOUR, -4, GETDATE()); DECLARE @end DATETIME = GETDATE(); EXEC xp_readerrorlog 0, 1, NULL, NULL, @start, @end, 'DESC'; -- Check previous log file (log rotates on restart) EXEC xp_readerrorlog 1, 1, NULL, NULL, NULL, NULL, 'DESC'; -- How many error log files exist EXEC xp_enumerrorlogs; ``` --- ## 3. Database Health ```sql -- Status and log reuse reason for all databases SELECT name, state_desc, -- Should be ONLINE; SUSPECT/EMERGENCY = problem recovery_model_desc, log_reuse_wait_desc, -- Why log space can't be reused (see below) is_read_only, is_auto_close_on, -- Should be OFF in production is_auto_shrink_on, -- Should be OFF in production compatibility_level -- SQL Server version compatibility FROM sys.databases ORDER BY name; ``` **`log_reuse_wait_desc` values:** | Value | Meaning | Fix | |-------|---------|-----| | `NOTHING` | Log can be reused — healthy | — | | `LOG_BACKUP` | Waiting for a log backup (FULL recovery mode) | Take a transaction log backup | | `CHECKPOINT` | Waiting for checkpoint | Run `CHECKPOINT` or wait | | `ACTIVE_TRANSACTION` | Long-running open transaction | Find and close the transaction | | `DATABASE_MIRRORING` | Mirroring partner is behind | Check mirroring latency | | `REPLICATION` | Replication not read | Check distributor | | `AVAILABILITY_REPLICA` | AG secondary is behind | Check AG health | --- ## 4. Disk Space ```sql -- Data and log file sizes and free space SELECT DB_NAME(mf.database_id) AS database_name, mf.name AS logical_name, mf.physical_name, mf.type_desc, mf.size * 8 / 1024 AS allocated_mb, (mf.size * 8 / 1024) - (FILEPROPERTY(mf.name, 'SpaceUsed') * 8 / 1024) AS free_mb, FILEPROPERTY(mf.name, 'SpaceUsed') * 8 / 1024 AS used_mb, CASE mf.is_percent_growth WHEN 1 THEN CAST(mf.growth AS VARCHAR) + '%' ELSE CAST(mf.growth * 8 / 1024 AS VARCHAR) + ' MB' END AS auto_growth FROM sys.master_files mf ORDER BY database_name, mf.type_desc; ``` --- ## 5. Blocking Analysis **Script:** `../scripts/blocking-analysis.sql` ### Quick Blocking Check ```sql -- Sessions that are blocked and what's blocking them SELECT r.session_id, r.blocking_session_id, r.wait_type, r.wait_time / 1000.0 AS wait_time_s, r.total_elapsed_time / 1000.0 AS elapsed_s, SUBSTRING(t.text, (r.statement_start_offset/2)+1, ((CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(t.text) ELSE r.statement_end_offset END - r.statement_start_offset)/2)+1) AS blocked_query, s.login_name, s.host_name FROM sys.dm_exec_requests r JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.blocking_session_id > 0 ORDER BY r.wait_time DESC; ``` ### Find the Head Blocker ```sql -- The session blocking all others (head of the chain) SELECT s.session_id, s.login_name, s.host_name, s.program_name, s.status, t.text AS current_or_last_query FROM sys.dm_exec_sessions s LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, s.sql_handle)) t WHERE s.session_id IN ( -- Sessions that ARE blocking someone but are NOT themselves blocked SELECT DISTINCT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0 ) AND s.session_id NOT IN ( SELECT session_id FROM sys.dm_exec_requests WHERE blocking_session_id > 0 ); ``` ⚠️ **Using KILL:** Only a human DBA issues KILL; agents and automated processes never do. A human DBA must obtain exact-target review and application-owner approval. Recheck login, host, program, transaction and connection start time immediately before acting: session IDs can be reused. Never target system sessions. Prefer waiting or application-level cancellation when safe. Use least privilege: ALTER ANY CONNECTION on SQL Server, not a blanket sysadmin grant. Uncommitted work is lost; rollback can take longer than the original transaction and blocking can persist during rollback. There is no undo for KILL; plan application recovery and monitor the rollback with `KILL <session_id> WITH STATUSONLY` rather than repeating a bare KILL against a reused ID. ```sql -- Human DBA preflight only: replace NULL with the reviewed session ID. -- Confirm sys.dm_exec_sessions.is_user_process = 1; no row means stop. -- Recheck identity immediately before acting; this snapshot is not authorization. DECLARE @review_session_id smallint = NULL; SELECT session_id, is_user_process, login_name, host_name, program_name, login_time, open_transaction_count FROM sys.dm_exec_sessions WHERE session_id = @review_session_id AND is_user_process = 1; ``` ```sql -- KILL should only be used after business confirmation that the blocking transaction -- can be safely terminated. Killing a session rolls back all open transactions. -- Agents and automated processes never execute KILL, even with approval. -- KILL 57; -- Replace 57 with actual session_id ``` --- ## 6. Deadlock Detection ### Read from System Health Session SQL Server captures deadlock graphs automatically in the `system_health` extended events session. ```sql -- Read deadlock events from system_health ring buffer SELECT xdr.value('@timestamp', 'DATETIME2') AS deadlock_time, xdr.query('.') AS deadlock_graph_xml FROM ( SELECT CAST(target_data AS XML) AS target_data FROM sys.dm_xe_session_targets t JOIN sys.dm_xe_sessions s ON t.event_session_address = s.address WHERE s.name = 'system_health' AND t.target_name = 'ring_buffer' ) AS data CROSS APPLY target_data.nodes('//RingBufferTarget/event[@name="xml_deadlock_report"]') AS xdt(xdr) ORDER BY deadlock_time DESC; ``` Click the `deadlock_graph_xml` result in SSMS to view the graphical deadlock graph. ### Read Deadlock Graph In the deadlock graph: - **Ovals** = transactions/processes - **Rectangles** = resources (locks) - **Arrows** = "this process owns this lock" and "this process is waiting for this lock" - The process marked with the **X** was chosen as the deadlock victim (killed by SQL Server) **Common deadlock patterns:** 1. **Update order deadlock** — Two sessions update the same two tables in different order. Fix: standardize update order. 2. **Reader-writer deadlock** — Reader holds shared lock, writer needs exclusive. Fix: add indexes to reduce scan time; use RCSI. 3. **Foreign key deadlock** — Parent-child insert/delete in different order. Fix: index foreign key columns on child table. --- ## 7. Long-Running Transactions ```sql -- Transactions open longer than 5 minutes SELECT s.session_id, s.login_name, s.host_name, at.transaction_begin_time, DATEDIFF(MINUTE, at.transaction_begin_time, GETDATE()) AS open_minutes, at.transaction_type, at.transaction_state, t.text AS last_query FROM sys.dm_tran_active_transactions at JOIN sys.dm_tran_session_transactions st ON at.transaction_id = st.transaction_id JOIN sys.dm_exec_sessions s ON st.session_id = s.session_id LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id OUTER APPLY sys.dm_exec_sql_text(COALESCE(r.sql_handle, s.sql_handle)) t WHERE DATEDIFF(MINUTE, at.transaction_begin_time, GETDATE()) > 5 ORDER BY open_minutes DESC; ``` **Long-running transactions cause:** - Log space not being reused (`log_reuse_wait_desc = ACTIVE_TRANSACTION`) - Version store bloat in TempDB (if RCSI is enabled) - Blocking other sessions Fix: investigate whether the transaction is in an application that forgot to commit, or whether a stored procedure is running longer than expected.
View on GitHub