mediumLocking

LCK_M_S_LOW_PRIORITY Wait Type Explained

Diagnose and fix LCK_M_S_LOW_PRIORITY waits in SQL Server. Learn root causes, diagnostic queries, and prevention strategies for low priority locking issues.

Quick Answer

LCK_M_S_LOW_PRIORITY occurs when a session waits for a shared lock using SQL Server 2014's low priority locking feature, typically during ALTER TABLE or ALTER INDEX operations with WAIT_AT_LOW_PRIORITY specified. This wait indicates the operation is yielding to higher priority transactions and will eventually timeout or succeed when conflicting locks clear.

Root Cause Analysis

The low priority locking mechanism was introduced in SQL Server 2014 to address blocking scenarios during online index rebuilds and table alterations. When you specify WAIT_AT_LOW_PRIORITY with MAX_DURATION and ABORT_AFTER_WAIT options, SQL Server's lock manager implements a two-phase acquisition strategy.

During the first phase, the operation attempts normal lock acquisition. If blocked, it enters the second phase where it waits at low priority for the specified MAX_DURATION. The lock manager places these requests in a separate low priority queue within the lock compatibility matrix, allowing existing and new higher priority requests to jump ahead.

The wait type surfaces when the requesting session enters this low priority wait state. SQL Server 2016 enhanced this behavior by improving the lock escalation detection during low priority waits. SQL Server 2019 added better integration with Query Store for tracking these operations, while SQL Server 2022 improved the internal scheduling efficiency for low priority lock requests.

The lock manager coordinates with the scheduler to periodically check if the low priority request can proceed. If the MAX_DURATION expires, the ABORT_AFTER_WAIT setting determines whether to kill blockers (BLOCKERS), kill the low priority session itself (SELF), or exit without action (NONE).

AutoDBA checks Low priority lock configurations, maintenance window scheduling, and blocking session detection across your entire SQL Server instance in 60 seconds. Download the free diagnostic script and see what else needs attention.

Diagnostic Queries

-- Current low priority lock waits with operation details
SELECT 
    r.session_id,
    r.blocking_session_id,
    r.wait_type,
    r.wait_time,
    r.wait_resource,
    t.text AS current_statement,
    r.command,
    r.percent_complete
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.wait_type = 'LCK_M_S_LOW_PRIORITY';
-- Lock details for low priority operations
SELECT 
    l.resource_type,
    l.resource_database_id,
    l.resource_associated_entity_id,
    l.request_mode,
    l.request_type,
    l.request_status,
    l.request_session_id,
    OBJECT_NAME(l.resource_associated_entity_id, l.resource_database_id) AS object_name
FROM sys.dm_tran_locks l
INNER JOIN sys.dm_exec_requests r ON l.request_session_id = r.session_id
WHERE r.wait_type = 'LCK_M_S_LOW_PRIORITY';
-- Historical low priority wait statistics
SELECT 
    wait_type,
    waiting_tasks_count,
    wait_time_ms,
    max_wait_time_ms,
    signal_wait_time_ms,
    wait_time_ms - signal_wait_time_ms AS resource_wait_time_ms
FROM sys.dm_os_wait_stats 
WHERE wait_type = 'LCK_M_S_LOW_PRIORITY';
-- Active transactions blocking low priority requests
SELECT 
    s.session_id,
    s.login_name,
    s.program_name,
    s.host_name,
    t.transaction_begin_time,
    t.transaction_type,
    t.transaction_state,
    r.command,
    r.status
FROM sys.dm_tran_active_transactions t
INNER JOIN sys.dm_tran_session_transactions st ON t.transaction_id = st.transaction_id
INNER 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
WHERE s.session_id IN (
    SELECT DISTINCT blocking_session_id 
    FROM sys.dm_exec_requests 
    WHERE wait_type = 'LCK_M_S_LOW_PRIORITY' 
    AND blocking_session_id IS NOT NULL
);

Fix Scripts

Adjust Low Priority Settings for Current Operations

-- Monitor and potentially cancel long-running low priority operations
-- Review current operations first, then decide on action
DECLARE @session_id INT;
SELECT @session_id = session_id 
FROM sys.dm_exec_requests 
WHERE wait_type = 'LCK_M_S_LOW_PRIORITY' 
AND wait_time > 300000; -- 5 minutes

IF @session_id IS NOT NULL
BEGIN
    PRINT 'Killing session ' + CAST(@session_id AS VARCHAR(10)) + ' due to excessive low priority wait';
    -- KILL @session_id; -- Uncomment after verification
END

Test the session identification logic in dev first. Killing sessions terminates user work.

Kill Blocking Transactions for Critical Operations

-- Identify and terminate specific blocking sessions
-- Use only when low priority operation is critical and blockers are non-essential
SELECT 
    'KILL ' + CAST(blocking_session_id AS VARCHAR(10)) + ';' AS kill_command,
    s.login_name,
    s.program_name,
    r.wait_time
FROM sys.dm_exec_requests r
INNER JOIN sys.dm_exec_sessions s ON r.blocking_session_id = s.session_id
WHERE r.wait_type = 'LCK_M_S_LOW_PRIORITY'
AND r.wait_time > 600000 -- 10 minutes
AND s.session_id > 50; -- Avoid system sessions

-- Execute the generated KILL commands manually after review

Always verify what the blocking sessions are doing before killing them. Document the business justification.

Implement Retry Logic for Failed Low Priority Operations

-- Template for rebuilding indexes with progressive timeout increases
DECLARE @max_duration INT = 5; -- Start with 5 minutes
DECLARE @retry_count INT = 0;
DECLARE @max_retries INT = 3;

WHILE @retry_count < @max_retries
BEGIN
    BEGIN TRY
        EXEC('ALTER INDEX IX_YourIndex ON dbo.YourTable REBUILD 
              WITH (ONLINE = ON, 
                    WAIT_AT_LOW_PRIORITY (MAX_DURATION = ' + CAST(@max_duration AS VARCHAR(10)) + ' MINUTES, 
                                         ABORT_AFTER_WAIT = SELF))');
        BREAK; -- Success, exit loop
    END TRY
    BEGIN CATCH
        SET @retry_count = @retry_count + 1;
        SET @max_duration = @max_duration * 2; -- Double the wait time
        WAITFOR DELAY '00:02:00'; -- Wait 2 minutes before retry
        
        IF @retry_count >= @max_retries
            THROW; -- Re-throw the last error
    END CATCH
END

Test the retry intervals and maximum duration values in dev. Adjust based on your maintenance window constraints.

AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.

Prevention

Configure online index operations during low-activity periods and use appropriate WAIT_AT_LOW_PRIORITY settings. Set MAX_DURATION based on your maintenance window length, typically 10-30 minutes for most environments. Use ABORT_AFTER_WAIT = BLOCKERS only when you can tolerate killing user sessions.

Monitor transaction log usage patterns to identify long-running transactions that commonly block maintenance operations. Implement application-level transaction boundaries to minimize lock duration. Consider using snapshot isolation levels for read operations that might conflict with maintenance tasks.

Establish automated monitoring for operations using low priority locks. Alert when wait times exceed 50% of the configured MAX_DURATION. Create runbooks for handling blocking scenarios during critical maintenance windows.

For highly concurrent OLTP systems, schedule index maintenance during dedicated maintenance windows or use partition switching strategies instead of online rebuilds. Configure Resource Governor to limit the impact of maintenance operations on production workloads.

Need hands-on help?

Dealing with persistent lck_m_s_low_priority issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.

Related Pages