Quick Answer
LCK_M_RX_U waits occur when a session needs to acquire an Update lock on a specific key value plus an Exclusive range lock between the current and previous keys in a serializable isolation level transaction. This wait type appears primarily during range scan operations with concurrent modifications and indicates lock contention on ordered data structures like indexes.
Root Cause Analysis
LCK_M_RX_U waits manifest when SQL Server's lock manager cannot immediately grant a combination of row-level Update locks and range locks during serializable transactions. The "RX_U" designation breaks down as Range eXclusive with Update lock, meaning the requesting session needs exclusive control over a key range while also requiring update permissions on the specific key value.
This wait occurs exclusively under SERIALIZABLE isolation level or when HOLDLOCK hints are specified. The lock manager must coordinate between the buffer pool's data pages and the lock hash table to ensure no phantom reads occur within the specified range. The requesting session blocks until all conflicting Shared, Update, or Exclusive locks are released from both the target key and the range between adjacent keys.
SQL Server 2016 introduced improved lock escalation algorithms that reduced some RX_U contention patterns, particularly in columnstore scenarios. SQL Server 2019's intelligent query processing can sometimes avoid these waits entirely by choosing different execution plans that minimize serializable range scanning. SQL Server 2022 and 2025 maintain the same core locking behavior but benefit from improved lock manager efficiency in high-concurrency scenarios.
The wait specifically occurs when the lock compatibility matrix prevents immediate lock grants. Existing Shared locks from other readers block the Update component, while any overlapping range locks from concurrent serializable transactions block the Range Exclusive component.
AutoDBA checks Lock contention patterns, isolation level settings, and snapshot isolation configuration across your entire SQL Server instance in 60 seconds. Download the free diagnostic script and see what else needs attention.
Diagnostic Queries
-- Identify sessions currently experiencing LCK_M_RX_U waits
SELECT
r.session_id,
r.wait_type,
r.wait_time,
r.blocking_session_id,
t.text AS sql_text,
r.database_id,
DB_NAME(r.database_id) AS database_name
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.wait_type = 'LCK_M_RX_U';
-- Examine lock details for RX_U waits including resource information
SELECT
l.request_session_id,
l.resource_type,
l.resource_database_id,
l.resource_description,
l.request_mode,
l.request_type,
l.request_status,
OBJECT_NAME(l.resource_associated_entity_id, l.resource_database_id) AS object_name
FROM sys.dm_tran_locks l
WHERE l.request_mode LIKE '%RX%' OR l.request_mode LIKE '%U%'
ORDER BY l.request_session_id;
-- Analyze blocking chains involving RX_U locks
WITH BlockingHierarchy AS (
SELECT
session_id,
blocking_session_id,
wait_type,
wait_time,
sql_handle,
0 AS level
FROM sys.dm_exec_requests
WHERE blocking_session_id = 0 AND session_id IN
(SELECT DISTINCT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id <> 0)
UNION ALL
SELECT
r.session_id,
r.blocking_session_id,
r.wait_type,
r.wait_time,
r.sql_handle,
bh.level + 1
FROM sys.dm_exec_requests r
INNER JOIN BlockingHierarchy bh ON r.blocking_session_id = bh.session_id
)
SELECT
bh.level,
bh.session_id,
bh.blocking_session_id,
bh.wait_type,
bh.wait_time,
t.text AS sql_text
FROM BlockingHierarchy bh
CROSS APPLY sys.dm_exec_sql_text(bh.sql_handle) t
WHERE bh.wait_type = 'LCK_M_RX_U' OR bh.session_id IN
(SELECT blocking_session_id FROM sys.dm_exec_requests WHERE wait_type = 'LCK_M_RX_U')
ORDER BY bh.level, bh.session_id;
-- Historical RX_U wait analysis from Query Store
SELECT
qsws.plan_id,
qsws.wait_category_desc,
qsws.total_query_wait_time_ms,
qsws.avg_query_wait_time_ms,
qsws.execution_count,
qsq.query_sql_text
FROM sys.query_store_wait_stats qsws
JOIN sys.query_store_query qsq ON qsws.query_id = qsq.query_id
WHERE qsws.wait_category_desc = 'Lock'
AND qsws.avg_query_wait_time_ms > 100
ORDER BY qsws.avg_query_wait_time_ms DESC;
-- Identify tables and indexes most affected by RX_U contention
SELECT
OBJECT_SCHEMA_NAME(l.resource_associated_entity_id, l.resource_database_id) AS schema_name,
OBJECT_NAME(l.resource_associated_entity_id, l.resource_database_id) AS table_name,
l.resource_type,
l.resource_description,
COUNT(*) AS lock_count,
COUNT(CASE WHEN l.request_status = 'WAIT' THEN 1 END) AS waiting_requests
FROM sys.dm_tran_locks l
WHERE l.request_mode LIKE '%RX%'
AND l.resource_associated_entity_id > 0
GROUP BY l.resource_associated_entity_id, l.resource_database_id, l.resource_type, l.resource_description
HAVING COUNT(CASE WHEN l.request_status = 'WAIT' THEN 1 END) > 0
ORDER BY waiting_requests DESC;
Fix Scripts
Reduce Isolation Level for Non-Critical Queries This script helps identify queries that can safely use READ COMMITTED instead of SERIALIZABLE, eliminating RX_U waits for read operations.
-- Template for converting SERIALIZABLE queries to READ COMMITTED
-- WARNING: Test thoroughly - this changes transaction isolation behavior
-- Only use if phantom reads are acceptable for the business logic
-- Current problematic pattern:
-- SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
-- SELECT * FROM Orders WHERE CustomerID = @CustomerID
-- Recommended replacement:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM Orders WITH (READCOMMITTED)
WHERE CustomerID = @CustomerID;
-- For existing stored procedures, add this at the beginning:
-- ALTER PROCEDURE [procedure_name]
-- AS
-- SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- [existing procedure logic]
Implement Row Versioning to Reduce Lock Contention Enables snapshot isolation to eliminate most reader-writer blocking scenarios causing RX_U waits.
-- Enable snapshot isolation on the database
-- WARNING: Increases tempdb usage and may impact performance initially
-- Test during low-traffic periods first
USE master;
GO
ALTER DATABASE [YourDatabaseName] SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE [YourDatabaseName] SET READ_COMMITTED_SNAPSHOT ON;
-- Monitor tempdb growth after implementation
SELECT
DB_NAME() AS database_name,
SUM(CASE WHEN type = 0 THEN size END) * 8 / 1024 AS data_size_mb,
SUM(CASE WHEN type = 1 THEN size END) * 8 / 1024 AS log_size_mb
FROM sys.database_files
WHERE name = 'tempdev' OR name = 'templog';
Optimize Index Strategy for Range Queries Creates covering indexes to minimize lock duration and scope for serializable range scans.
-- Analyze missing indexes that could reduce RX_U contention
-- This reduces the number of keys that need range locking
SELECT
migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure,
'CREATE INDEX IX_' + OBJECT_NAME(mid.object_id) + '_' + REPLACE(REPLACE(REPLACE(ISNULL(mid.equality_columns,''),', ','_'),'[',''),']','') +
CASE WHEN mid.inequality_columns IS NOT NULL THEN '_' + REPLACE(REPLACE(REPLACE(mid.inequality_columns,', ','_'),'[',''),']','') ELSE '' END +
' ON ' + mid.statement +
' (' + ISNULL (mid.equality_columns,'') +
CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END +
CASE WHEN mid.inequality_columns IS NOT NULL THEN mid.inequality_columns ELSE '' END +
')' +
CASE WHEN mid.included_columns IS NOT NULL THEN ' INCLUDE (' + mid.included_columns + ')' ELSE '' END AS create_index_statement
FROM sys.dm_db_missing_index_groups mig
INNER JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) > 10
ORDER BY improvement_measure DESC;
Implement Lock Timeout for Problem Queries Prevents indefinite blocking by setting reasonable timeout values for queries prone to RX_U waits.
-- Set lock timeout to prevent indefinite waits
-- WARNING: Applications must handle timeout errors gracefully
-- Implement retry logic in application code
SET LOCK_TIMEOUT 30000; -- 30 seconds
-- Example implementation with error handling
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
-- Your serializable query here
SELECT * FROM Orders WHERE OrderDate BETWEEN @StartDate AND @EndDate;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
-- Handle lock timeout (error 1222)
IF ERROR_NUMBER() = 1222
BEGIN
PRINT 'Lock timeout occurred - consider retry or alternative approach';
-- Log the event for analysis
END
ELSE
BEGIN
-- Handle other errors
THROW;
END
END CATCH;
AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.
Prevention
Configure READ_COMMITTED_SNAPSHOT at the database level to eliminate most reader-writer blocking scenarios that cause RX_U waits. This architectural change provides point-in-time consistency without range locking overhead.
Design indexes specifically for range queries by including all filtering and sorting columns in the key, with frequently accessed columns in the INCLUDE clause. This minimizes the key range that requires exclusive locking during serializable transactions.
Implement application-level retry logic with exponential backoff for operations that occasionally encounter RX_U waits. Set LOCK_TIMEOUT to reasonable values (15-60 seconds) rather than allowing indefinite blocking.
Monitor for long-running serializable transactions using Query Store wait statistics. Queries averaging more than 100ms of lock waits should be candidates for isolation level reduction or index optimization.
Establish row versioning monitoring in tempdb after enabling snapshot isolation. Create alerts when tempdb grows beyond expected thresholds, indicating excessive version store usage that could impact overall system performance.
Consider partitioning large tables that frequently experience RX_U contention during range scans. Partition elimination reduces the scope of range locks by limiting serializable operations to relevant partitions only.
Need hands-on help?
Dealing with persistent lck_m_rx_u issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.