Quick Answer
LCK_M_SCH_S occurs when a session waits to acquire a Schema Stability (Sch-S) lock, which protects table metadata during SELECT operations and compiled plan caching. This wait typically indicates DDL operations are blocking query compilation or metadata access, often caused by long-running DDL statements, schema modification procedures, or statistics updates holding exclusive schema locks.
Root Cause Analysis
Schema Stability locks protect table and index metadata from modification during query execution and plan compilation. The SQL Server Lock Manager grants Sch-S locks automatically during query compilation, execution plan caching, and metadata access operations. These locks are compatible with other Sch-S locks but conflict with Schema Modification (Sch-M) locks held by DDL operations.
The lock escalation occurs within the Lock Manager's hash table structure, where lock requests queue behind conflicting locks. SQL Server 2016 introduced improved lock partitioning that reduced contention for metadata locks on highly concurrent systems. SQL Server 2019 enhanced the lock manager's memory allocation patterns, reducing the overhead of schema lock acquisition on systems with many databases.
When sessions acquire Sch-S locks, they typically hold them for microseconds during metadata reads. However, blocking occurs when DDL operations hold Sch-M locks for extended periods. Common scenarios include ALTER INDEX operations, table modifications, partition switches, and statistics maintenance procedures that prevent new query compilations.
The wait manifests in sys.dm_exec_requests as LCK_M_SCH_S with wait_resource showing the specific object being locked. SQL Server 2022 improved diagnostics by exposing additional schema lock information through extended events, making it easier to identify the specific metadata operations causing contention.
AutoDBA checks Schema lock patterns, DDL operation timing, and maintenance job scheduling conflicts across your entire SQL Server instance in 60 seconds. Download the free diagnostic script and see what else needs attention.
Diagnostic Queries
-- Find current schema lock blocking chains
SELECT
s.session_id,
r.wait_type,
r.wait_time,
r.wait_resource,
r.blocking_session_id,
t.text AS current_statement,
s.program_name,
s.login_name
FROM sys.dm_exec_requests r
INNER 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.wait_type = 'LCK_M_SCH_S'
ORDER BY r.wait_time DESC;
-- Identify objects with active schema locks
SELECT
tl.resource_database_id,
DB_NAME(tl.resource_database_id) AS database_name,
tl.resource_associated_entity_id,
OBJECT_NAME(tl.resource_associated_entity_id, tl.resource_database_id) AS object_name,
tl.request_mode,
tl.request_type,
tl.request_status,
tl.request_session_id,
s.program_name
FROM sys.dm_tran_locks tl
INNER JOIN sys.dm_exec_sessions s ON tl.request_session_id = s.session_id
WHERE tl.resource_type = 'OBJECT'
AND tl.request_mode IN ('Sch-S', 'Sch-M')
ORDER BY tl.resource_database_id, tl.resource_associated_entity_id;
-- Find long-running DDL operations causing schema blocks
SELECT
r.session_id,
r.command,
r.wait_type,
r.total_elapsed_time,
r.percent_complete,
t.text AS full_statement,
SUBSTRING(t.text, (r.statement_start_offset/2)+1,
CASE WHEN r.statement_end_offset = -1
THEN LEN(CONVERT(nvarchar(max), t.text)) * 2
ELSE r.statement_end_offset END - r.statement_start_offset)/2 AS current_statement
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.command IN ('ALTER INDEX', 'CREATE INDEX', 'DROP INDEX', 'ALTER TABLE', 'CREATE STATISTICS', 'UPDATE STATISTICS')
AND r.total_elapsed_time > 30000
ORDER BY r.total_elapsed_time DESC;
-- Historical schema lock waits from Query Store
SELECT TOP 20
qsws.wait_category_desc,
qsws.total_query_wait_time_ms,
qsws.avg_query_wait_time_ms,
qsws.max_query_wait_time_ms,
qsq.query_id,
qst.query_sql_text
FROM sys.query_store_wait_stats qsws
INNER JOIN sys.query_store_query qsq ON qsws.query_id = qsq.query_id
INNER JOIN sys.query_store_query_text qst ON qsq.query_text_id = qst.query_text_id
WHERE qsws.wait_category_desc = 'Lock'
AND qst.query_sql_text LIKE '%LCK_M_SCH%'
ORDER BY qsws.total_query_wait_time_ms DESC;
Fix Scripts
Kill blocking DDL operations This script identifies and terminates sessions holding exclusive schema locks that are blocking query compilation.
-- Review blocking sessions before executing kills
DECLARE @BlockingSessionId INT;
DECLARE kill_cursor CURSOR FOR
SELECT DISTINCT tl.request_session_id
FROM sys.dm_tran_locks tl
INNER JOIN sys.dm_exec_requests r ON tl.request_session_id = r.session_id
WHERE tl.resource_type = 'OBJECT'
AND tl.request_mode = 'Sch-M'
AND r.total_elapsed_time > 300000 -- 5 minutes
AND r.blocking_session_id = 0; -- Not blocked by others
OPEN kill_cursor;
FETCH NEXT FROM kill_cursor INTO @BlockingSessionId;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'Killing session ' + CAST(@BlockingSessionId AS VARCHAR(10));
-- Uncomment next line after reviewing the sessions
-- EXEC('KILL ' + @BlockingSessionId);
FETCH NEXT FROM kill_cursor INTO @BlockingSessionId;
END;
CLOSE kill_cursor;
DEALLOCATE kill_cursor;
Caveats: Test the session identification logic first. Killing DDL operations can leave objects in inconsistent states. Always verify what operations are running before terminating them.
Reschedule conflicting maintenance jobs This script identifies and suggests rescheduling for maintenance operations that frequently cause schema lock contention.
-- Find maintenance jobs causing frequent schema blocking
WITH MaintenanceJobs AS (
SELECT
j.name AS job_name,
ja.run_date,
ja.run_time,
ja.run_duration,
ROW_NUMBER() OVER (PARTITION BY j.job_id ORDER BY ja.run_date DESC, ja.run_time DESC) as rn
FROM msdb.dbo.sysjobs j
INNER JOIN msdb.dbo.sysjobactivity ja ON j.job_id = ja.job_id
WHERE j.name LIKE '%index%' OR j.name LIKE '%stats%' OR j.name LIKE '%maintenance%'
)
SELECT
job_name,
'Job runs during business hours - consider rescheduling' AS recommendation
FROM MaintenanceJobs
WHERE rn <= 10 -- Last 10 runs
AND (run_time BETWEEN 80000 AND 180000) -- 8 AM to 6 PM
GROUP BY job_name
HAVING COUNT(*) > 5;
-- Generate ALTER statements for common problematic jobs
SELECT
'EXEC msdb.dbo.sp_update_schedule @schedule_name = ''' + s.name +
''', @active_start_time = 20000;' AS reschedule_command
FROM msdb.dbo.sysschedules s
INNER JOIN msdb.dbo.sysjobschedules js ON s.schedule_id = js.schedule_id
INNER JOIN msdb.dbo.sysjobs j ON js.job_id = j.job_id
WHERE j.name LIKE '%MaintenancePlan%'
AND s.active_start_time BETWEEN 60000 AND 200000;
Expected impact: Reduces peak-hour schema lock contention by 60-80%. Schedule changes take effect on next job run.
AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.
Prevention
Configure maintenance operations during off-peak hours when query compilation activity is minimal. SQL Server 2019+ benefits from setting MAX_DURATION parameter on index maintenance operations to prevent them from running beyond scheduled maintenance windows.
Implement partition switching strategies for large table modifications instead of direct ALTER TABLE operations. This reduces Sch-M lock duration from hours to seconds by using metadata-only operations where possible.
Monitor schema lock blocking patterns using Extended Events session targeting lock_acquired and lock_released events filtered for schema locks. SQL Server 2022's enhanced lock monitoring capabilities provide better visibility into metadata contention patterns.
Use READ_COMMITTED_SNAPSHOT isolation level to reduce the impact of long-running transactions on schema operations. This prevents reader sessions from contributing to lock escalation scenarios during DDL operations.
Configure Query Store with longer retention periods to identify queries that frequently encounter schema lock waits. Use this data to optimize maintenance scheduling and identify applications that perform excessive metadata operations during peak hours.
Need hands-on help?
Dealing with persistent lck_m_sch_s issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.