LATCH_EX or LATCH_SH near the top of a wait report is a starting point, not a diagnosis. Latch waits protect internal SQL Server structures, and the general wait name does not identify which structure is busy. sys.dm_os_latch_stats lets you read the latch classes and choose a useful next check.

Separate Latches From Locks and I/O Latches
A lock coordinates transaction access to data. A latch protects an internal structure for a shorter critical section. A page latch protects a buffer page in memory, while a page I/O latch can reflect waiting for storage. The LATCH_EX and LATCH_SH waits discussed here are general latch waits; do not combine them with LCK_M or PAGELATCH totals and call the sum one problem.
I have seen a chart lead directly to an index change without anyone checking the latch class. That can be an expensive detour. First confirm the time window and whether the waits are active now. What changed when the wait rate rose: query shape, file growth, maintenance, or workload volume?
Capture a Baseline of Latch Waits by Class
sys.dm_os_latch_stats has one row per latch class, with request counts and accumulated milliseconds. Its values accumulate since the counters were initialized and can reset. Copy a first sample into a temporary table, wait a few minutes during the incident, and take a second sample. The difference is more useful than sorting the lifetime total.
SELECT latch_class, waiting_requests_count,
wait_time_ms, max_wait_time_ms
INTO #LatchStart
FROM sys.dm_os_latch_stats;
WAITFOR DELAY '00:02:00';
SELECT s.latch_class,
e.waiting_requests_count - s.waiting_requests_count
AS requests_delta,
e.wait_time_ms - s.wait_time_ms AS wait_ms_delta,
(e.wait_time_ms - s.wait_time_ms) * 1.0 /
NULLIF(e.waiting_requests_count - s.waiting_requests_count, 0)
AS avg_wait_ms
FROM #LatchStart AS s
JOIN sys.dm_os_latch_stats AS e
ON e.latch_class = s.latch_class
WHERE e.wait_time_ms >= s.wait_time_ms
AND e.waiting_requests_count >= s.waiting_requests_count
AND s.latch_class <> N'BUFFER'
ORDER BY wait_ms_delta DESC;Run the sample in a dedicated query window and drop the temporary table before repeating it. A negative delta indicates a reset or other discontinuity, so the filter excludes it. The BUFFER class is left out too, because it holds the page latches that surface under the PAGELATCH and PAGEIOLATCH names. Record server start time, sample times, workload volume, and CPU alongside the result. A class with a high lifetime total but no new waits is not today's lead.
Read ACCESS_METHODS_DATASET_PARENT
This class coordinates access between parent and child datasets in parallel operations. A rise can point toward parallel scans or other parallel query activity. It does not mean that every parallel query is broken. Correlate the interval with active requests, Query Store plans, scan counts, degree of parallelism, and workload changes. A single report starting a large scan can shift the class total.
SELECT r.session_id, r.command, r.wait_type,
r.cpu_time, r.total_elapsed_time,
r.dop, t.text AS batch_text
FROM sys.dm_exec_requests AS r
JOIN sys.dm_exec_sessions AS s
ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE s.is_user_process = 1
ORDER BY r.cpu_time DESC;The is_user_process filter matters, because background tasks also hold session IDs above 50. The dop column is available on supported modern versions; remove it when testing an older instance that lacks it. Inspect the actual plan for a hot query before changing MAXDOP. A blanket instance setting made from one class counter can slow unrelated work. The next question is which query increased parallel dataset activity in the measured interval.
Read FGCB_ADD_REMOVE
FGCB_ADD_REMOVE synchronizes filegroup add, remove, grow, and shrink operations. A rising delta sends me to file events: data-file autogrowth, repeated small growth increments, a shrink job, or a file operation in progress. Check current file sizes, growth settings, free space, and SQL Server error log entries. Pre-size files based on measured demand rather than suppressing autogrowth altogether.
SELECT DB_NAME(database_id) AS database_name,
name, type_desc, size * 8.0 / 1024 AS size_mb,
growth, is_percent_growth
FROM sys.master_files
WHERE database_id = DB_ID()
ORDER BY file_id;This file query shows configuration, not a history of growth events. Use a captured event stream, error log, or operational monitoring to line up growth with the latch sample. A busy class is an association; an observed growth event in the same interval is stronger evidence. Small fixed growth and a full volume deserve attention before changing a query plan.

Treat LOG_MANAGER as an Internal Clue
Microsoft documents LOG_MANAGER as an internal-use latch class. Its name alone does not prove that the transaction log file is growing. Check log growth events, write latency, log usage, and WRITELOG waits before assigning a cause. A log file expanding every few minutes is one plausible path to investigate, but only the correlated evidence can support it.
SELECT DB_NAME() AS database_name,
total_log_size_in_bytes / 1048576.0 AS total_log_mb,
used_log_space_in_bytes / 1048576.0 AS used_log_mb,
used_log_space_in_percent
FROM sys.dm_db_log_space_usage;The DMV is scoped to the current database, so run it in the database under investigation. Pair it with file-growth history and log I/O statistics. If LOG_MANAGER rises without growth, look at the concurrent log workload and do not force the data-file explanation onto it. Class labels are clues, not complete root-cause descriptions.
Act on Latch Waits, Then Measure Again
For parallel dataset waits, inspect the hot parallel plan and its reads. For filegroup waits, correct repeated growth with sensible sizing and remove avoidable shrink cycles. For log activity, test log file sizing and storage latency against the incident timeline. Make one targeted change, then repeat the same class-delta sample under comparable load.
I record the top classes, deltas, queries or growth events, action, and new deltas in one note. If waits fall only because traffic fell, the incident is not solved. If the class rate falls and user latency improves at the same workload level, the evidence is stronger. Keep the general LATCH name as the signpost, and let the class plus a second observation carry the diagnosis.
Verify the Sample Window for Latch Waits
A two-minute sample can miss a burst that lasted only ten seconds. If users report intermittent stalls, collect repeated short intervals or use an event session that captures file growth and relevant waits. Keep the clock source consistent when comparing with application timestamps. A chart from the wrong hour can make an unrelated growth event look causal.
The average wait is total wait time divided by requests in the interval; it can hide one severe outlier. Read maximum wait time and the distribution available from other monitoring before declaring the class harmless. Conversely, a high request count with tiny waits can contribute little to user latency. Rank by time and affected requests, then connect the class to a visible workload.
Preserve the Before Picture
Before changing growth increments or MAXDOP, save the two class samples, file state, active requests, and the user-facing symptom. Repeating the same query after a change under a different workload is not a fair comparison. If volume changed, normalize the waits by batch count or completed business operations, and label that estimate. This small evidence set keeps a plausible story from hardening into an unsupported root cause.
Related reading on this blog: What is Latch? and Timeout Occurred While Waiting for Latch: Class FGCB_ADD_REMOVE.

A latch wait class is not a diagnosis, it is a pointer to the next measurement.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.




