Programming language used to interact with SQL Server databases
- Does WITH (ROWLOCK) always acquire only row-level locks?
No. Per Microsoft, if you use a lock hint such as ROWLOCK, this only alters the initial lock plan; lock hints don't prevent lock escalation. It influences the starting granularity, not the transaction's entire lifetime.
- Can SQL Server still acquire page locks despite ROWLOCK?
Yes. The hint requests row granularity as the starting point but does not prevent the engine from taking intent locks on pages/object, nor from escalating. ROWLOCK only suppresses page locks as the initial choice.
- Can row locks escalate to OBJECT/TABLE during the update?
Yes. ROWLOCK does not disable escalation. A large UPDATE accumulating enough locks on a single object will trigger an escalation attempt to TABLE regardless of the hint.
- Documented scenarios where the requested granularity is overridden?
Yes. escalation triggers via two independent paths. The lock-count threshold, and memory pressure: a memory threshold of 40 percent of lock memory, which equates to roughly 24 percent of the buffer pool, can trigger escalation. Also ALTER TABLE ... SET (LOCK_ESCALATION = ...), lock-memory exhaustion (1204 errors), and non-SARGable predicates forcing large scans.
- Version differences - 2019, 2022, Azure SQL, and optimized locking:
Per Microsoft Learn, it applies to SQL Server 2025 (17.x), Azure SQL Database, Azure SQL Managed Instance, and SQL database in Microsoft Fabric.
Optimized locking avoids lock escalations and holds very few locks even for large transactions. So under optimized locking the ROWLOCK hint becomes largely moot for escalation concerns - the row/page locks are released almost immediately rather than accumulating toward a threshold. Two enablement caveats: ADR is absolutely necessary to get optimized locking working, and RCSI enables the lock-after-qualification component. Also note: if RCSI is enabled, use the READCOMMITTEDLOCK table hint to force blocking between two queries when optimized locking is enabled.
Under optimized locking (Azure SQL, or 2025 with it enabled): The 25,000-row UPDATE might still acquire 25,000 X row locks, but each is released as soon as its row is updated, and only one X TID lock is held until the end of the transaction. Escalation is largely avoided. The classic "batch under 5,000 rows" workaround becomes unnecessary in this mode.
To actually guarantee row-level locking / prevent escalation under traditional locking, the hint is insufficient. Use ALTER TABLE dbo.YourTable SET (LOCK_ESCALATION = DISABLE), or batch the UPDATE into chunks below the threshold.