Quick Answer
LCK_M_RIN_X occurs when a session waits to acquire an exclusive lock on a range-insert-NULL key within an index range, preventing phantom reads in serializable isolation level. This wait typically indicates high concurrency on range queries with serializable transactions or deadlock resolution involving range locks.
Root Cause Analysis
The lock manager generates LCK_M_RIN_X waits when SQL Server attempts to acquire an exclusive range-insert-NULL lock on an index key range. This lock type prevents phantom inserts within a specific range during serializable isolation level transactions or when explicit HOLDLOCK hints are specified.
Range-insert-NULL locks (RangeIn-Null) lock the gap before the first key in an index to prevent inserts that would create phantom rows. The "X" suffix indicates an exclusive lock mode, meaning no other transaction can read or modify the locked range. These locks are part of SQL Server's key-range locking mechanism, which combines traditional row locks with gap locks to provide serializable isolation.
The lock manager requests these locks during:
- SELECT statements with SERIALIZABLE isolation or HOLDLOCK hints
- Range scans that need to prevent phantom inserts
- Deadlock resolution when SQL Server promotes shared range locks to exclusive
- Foreign key constraint validation on the referenced table side
In SQL Server 2019 and later, the lock escalation engine more aggressively converts range locks when memory pressure exists in the lock hash table. SQL Server 2022 improved range lock compatibility with read committed snapshot isolation, reducing unnecessary exclusive range lock acquisitions.
The scheduler thread remains suspended until the lock manager either grants the exclusive range lock or detects a deadlock condition. Unlike row locks, range locks can span multiple physical pages and persist longer because they protect logical ranges rather than specific rows.
AutoDBA checks Transaction isolation levels, lock escalation settings, and range lock contention patterns across your entire SQL Server instance in 60 seconds. Download the free diagnostic script and see what else needs attention.
Diagnostic Queries
-- Identify sessions waiting for RIN_X locks with blocking details
SELECT
s.session_id,
r.blocking_session_id,
s.login_name,
r.wait_time,
r.wait_resource,
t.text AS current_statement,
s.transaction_isolation_level
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.wait_type = 'LCK_M_RIN_X';
-- Analyze range lock details and lock hierarchy
SELECT
l.request_session_id,
l.resource_type,
l.resource_database_id,
l.resource_associated_entity_id,
l.request_mode,
l.request_status,
o.name AS object_name,
i.name AS index_name
FROM sys.dm_tran_locks l
LEFT JOIN sys.objects o ON l.resource_associated_entity_id = o.object_id
LEFT JOIN sys.indexes i ON l.resource_associated_entity_id = i.object_id
AND l.resource_subtype = i.index_id
WHERE l.request_mode LIKE '%RangeIn%'
ORDER BY l.request_session_id;
-- Check for serializable transactions causing range locks
SELECT
s.session_id,
t.transaction_id,
s.transaction_isolation_level,
at.transaction_begin_time,
at.transaction_state,
s.last_request_start_time,
sqltext.text
FROM sys.dm_tran_session_transactions t
JOIN sys.dm_exec_sessions s ON t.session_id = s.session_id
JOIN sys.dm_tran_active_transactions at ON t.transaction_id = at.transaction_id
CROSS APPLY sys.dm_exec_sql_text(s.most_recent_sql_handle) sqltext
WHERE s.transaction_isolation_level = 4 -- SERIALIZABLE
ORDER BY at.transaction_begin_time;
-- Examine lock escalation and memory pressure indicators
SELECT
cntr_value AS lock_escalations_per_sec
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Lock Escalations/sec';
SELECT
lock_memory_used_kb = cntr_value / 1024
FROM sys.dm_os_performance_counters
WHERE counter_name = 'Lock Memory (KB)';
-- Identify indexes with high range lock contention
WITH RangeLockStats AS (
SELECT
l.resource_associated_entity_id,
COUNT(*) as lock_count,
COUNT(CASE WHEN l.request_status = 'WAIT' THEN 1 END) as waiting_locks
FROM sys.dm_tran_locks l
WHERE l.resource_type = 'KEY'
AND l.request_mode LIKE '%Range%'
GROUP BY l.resource_associated_entity_id
)
SELECT
o.name AS table_name,
i.name AS index_name,
rls.lock_count,
rls.waiting_locks,
i.type_desc
FROM RangeLockStats rls
JOIN sys.objects o ON rls.resource_associated_entity_id = o.object_id
JOIN sys.indexes i ON rls.resource_associated_entity_id = i.object_id
WHERE rls.waiting_locks > 0
ORDER BY rls.waiting_locks DESC;
Fix Scripts
Reduce Transaction Isolation Level Eliminates unnecessary range locks by changing from SERIALIZABLE to READ COMMITTED SNAPSHOT where phantom reads are acceptable.
-- Enable snapshot isolation database-wide (requires exclusive access)
ALTER DATABASE [YourDatabase] SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE [YourDatabase] SET READ_COMMITTED_SNAPSHOT ON;
-- Or modify application code to use lower isolation levels
-- SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
Test in development first. READ COMMITTED SNAPSHOT increases tempdb usage and may impact performance on high-write workloads.
Optimize Range Query Patterns Redesigns queries to use more selective predicates and avoid large range scans requiring serializable isolation.
-- Replace broad range queries with specific key lookups where possible
-- BEFORE: SELECT * FROM Orders WHERE OrderDate BETWEEN @start AND @end
-- AFTER: Break into smaller ranges or use pagination
-- Example pagination approach
DECLARE @PageSize INT = 1000;
WITH PagedResults AS (
SELECT OrderID, OrderDate,
ROW_NUMBER() OVER (ORDER BY OrderDate) as rn
FROM Orders
WHERE OrderDate >= @start
)
SELECT OrderID, OrderDate
FROM PagedResults
WHERE rn BETWEEN @PageOffset AND @PageOffset + @PageSize
ORDER BY OrderDate;
Reduces lock duration and eliminates need for range locks. Test performance impact of ROW_NUMBER() on large result sets.
Implement Optimistic Concurrency Control Replaces range locks with application-level concurrency checks using timestamp or version columns.
-- Add version column for optimistic concurrency
ALTER TABLE YourTable ADD VersionStamp TIMESTAMP;
-- Application code pattern for updates
BEGIN TRAN
DECLARE @CurrentVersion TIMESTAMP;
SELECT @CurrentVersion = VersionStamp
FROM YourTable
WHERE KeyColumn = @Key;
UPDATE YourTable
SET DataColumn = @NewValue
WHERE KeyColumn = @Key
AND VersionStamp = @CurrentVersion;
IF @@ROWCOUNT = 0
THROW 50001, 'Record was modified by another user', 1;
COMMIT TRAN
Eliminates range locks entirely but requires application logic changes. Test rollback handling for concurrent modifications.
Configure Lock Escalation Thresholds Adjusts when SQL Server escalates to table locks, potentially reducing range lock contention.
-- Disable lock escalation on specific tables with high range lock contention
ALTER TABLE YourTable SET (LOCK_ESCALATION = DISABLE);
-- Or escalate to partition level instead of table level
ALTER TABLE YourTable SET (LOCK_ESCALATION = AUTO);
-- Monitor escalation impact
SELECT
OBJECT_NAME(p.object_id) AS table_name,
p.lock_escalation_desc
FROM sys.partitions p
WHERE p.object_id = OBJECT_ID('YourTable');
Disabling escalation increases memory usage for locks. Monitor lock memory consumption after implementing.
AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.
Prevention
Configure applications to use READ COMMITTED SNAPSHOT isolation by default rather than SERIALIZABLE. This eliminates most range lock scenarios while maintaining acceptable consistency for most business logic.
Design indexes with leading columns that support equality predicates rather than range predicates. Composite indexes with highly selective leading columns reduce the scope of range locks when they do occur.
Implement connection pooling with short connection lifetimes to prevent long-running transactions that hold range locks. Transaction timeout settings should be aggressive enough to prevent abandoned transactions from blocking range lock requests.
Monitor lock memory usage and configure adequate memory allocation for the lock manager. Range locks consume more memory than row locks, so systems with frequent SERIALIZABLE transactions need higher lock memory allocations.
Partition large tables based on common query patterns to isolate range lock contention to specific partitions. This is particularly effective for date-based partitioning where range queries typically span single partitions.
Consider implementing application-level retry logic with exponential backoff for operations that may encounter range lock contention. This prevents cascading blocks when multiple sessions compete for the same range.
Use SQL Server 2022's improved lock monitoring features to establish baselines for normal range lock behavior and alert when patterns indicate emerging contention hotspots.
Need hands-on help?
Dealing with persistent lck_m_rin_x issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.