SQL Server 2025 Series : This New Locking Feature Changes Everything – Full Demo on Optimized Locking!

If you work with high-concurrency OLTP workloads, SQL Server 2025 Optimized Locking is one of the most practical improvements to understand. In this demo, we use two databases: one with optimized locking disabled and one with it enabled. Both databases are configured with Accelerated Database Recovery (ADR) and Read Committed Snapshot Isolation (RCSI), while only one database has OPTIMIZED_LOCKING = ON.

Why this feature matters

The real value of optimized locking is simple: it helps reduce lock footprint during write activity, which can lower blocking in busy systems. The attached demo is designed exactly for that comparison by creating two identical databases—Billing_OFF and Billing_ON—and toggling only the optimized locking setting between them.

Demo setup

Start by creating the two demo databases and enabling the required database options:

USE master;
GO
DROP DATABASE IF EXISTS Billing_OFF;
DROP DATABASE IF EXISTS Billing_ON;
GO
CREATE DATABASE Billing_OFF;
CREATE DATABASE Billing_ON;
GO
-- Both databases need ADR enabled
ALTER DATABASE Billing_OFF SET ACCELERATED_DATABASE_RECOVERY = ON;
ALTER DATABASE Billing_ON SET ACCELERATED_DATABASE_RECOVERY = ON;
GO
-- RCSI is needed for lock-after-qualification (LAQ) demo
ALTER DATABASE Billing_OFF SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
ALTER DATABASE Billing_ON SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
GO
-- Only one database gets optimized locking
ALTER DATABASE Billing_OFF SET OPTIMIZED_LOCKING = OFF;
ALTER DATABASE Billing_ON SET OPTIMIZED_LOCKING = ON;
GO
-- Verify settings
SELECT
name,
is_accelerated_database_recovery_on,
is_read_committed_snapshot_on,
is_optimized_locking_on
FROM sys.databases
WHERE name IN ('Billing_OFF', 'Billing_ON');
GO

Build the test table

Next, create the same table in both databases and open a transaction so you can inspect locks while the transaction is still active.

Setup for optimized locking OFF

USE Billing_OFF;
GO
DROP TABLE IF EXISTS dbo.InvoiceLedger;
GO
CREATE TABLE dbo.InvoiceLedger
(
InvoiceId int NOT NULL,
AmountDue decimal(10,2) NULL
);
GO
INSERT INTO dbo.InvoiceLedger (InvoiceId, AmountDue)
VALUES (1001, 1200.00),
(1002, 850.00),
(1003, 430.00);
GO
BEGIN TRAN;
UPDATE dbo.InvoiceLedger
SET AmountDue = AmountDue + 50.00;
SELECT
request_session_id,
resource_type,
request_mode,
resource_description
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID
AND resource_type IN ('PAGE', 'RID', 'KEY', 'XACT')
ORDER BY resource_type, request_mode;
-- Keep transaction open for observation
WAITFOR DELAY '00:00:20';
COMMIT TRAN;
GO

This script updates all rows and keeps the transaction open for 20 seconds so you can inspect the acquired locks in the Billing_OFF database.

Setup for optimized locking ON

USE Billing_ON;
GO
DROP TABLE IF EXISTS dbo.InvoiceLedger;
GO
CREATE TABLE dbo.InvoiceLedger
(
InvoiceId int NOT NULL,
AmountDue decimal(10,2) NULL
);
INSERT INTO dbo.InvoiceLedger (InvoiceId, AmountDue)
VALUES (1001, 1200.00),
(1002, 850.00),
(1003, 430.00);
GO
BEGIN TRAN;
UPDATE dbo.InvoiceLedger
SET AmountDue = AmountDue + 50.00;
SELECT
request_session_id,
resource_type,
request_mode,
resource_description
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID
AND resource_type IN ('PAGE', 'RID', 'KEY', 'XACT')
ORDER BY resource_type, request_mode;
WAITFOR DELAY '00:00:20';
COMMIT TRAN;
GO

This is the matching script for Billing_ON, allowing you to compare lock behavior when optimized locking is enabled.

Concurrency test

To demonstrate blocking behavior, open two sessions against each database.

Session 1 – hold an update open

Optimized locking OFF

USE Billing_OFF;
GO
BEGIN TRAN;
UPDATE dbo.InvoiceLedger
SET AmountDue = AmountDue + 25.00
WHERE InvoiceId = 1001;
WAITFOR DELAY '00:00:20';
COMMIT TRAN;
GO

This session updates InvoiceId = 1001 and intentionally holds the transaction for 20 seconds.

Optimized locking ON

USE Billing_ON;
GO
BEGIN TRAN;
UPDATE dbo.InvoiceLedger
SET AmountDue = AmountDue + 25.00
WHERE InvoiceId = 1001;
WAITFOR DELAY '00:00:20';
COMMIT TRAN;
GO

This is the same workload pattern, but executed in the database where optimized locking is enabled.

Session 2 – concurrent update

Optimized locking OFF

USE Billing_OFF;
GO
BEGIN TRAN;
UPDATE dbo.InvoiceLedger
SET AmountDue = AmountDue + 25.00
WHERE InvoiceId = 1002;
COMMIT TRAN;
GO

Run this in a second session while Session 1 is still open.

Optimized locking ON

USE Billing_ON;
GO
BEGIN TRAN;
UPDATE dbo.InvoiceLedger
SET AmountDue = AmountDue + 25.00
WHERE InvoiceId = 1002;
COMMIT TRAN;
GO

Again, this is the same concurrent update, but without optimized locking.

Monitoring script

Use the following monitoring query to observe waits, request state, and lock information for both sessions while the demo is running:

-- Replace the session IDs below with the two session IDs used in your demo windows.
SELECT
r.session_id,
r.status,
r.command,
r.wait_type,
r.wait_time,
r.wait_resource,
t.text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id IN (62, 171);
GO
SELECT
request_session_id,
resource_type,
request_mode,
request_status,
resource_description
FROM sys.dm_tran_locks
WHERE request_session_id IN (62, 171)
ORDER BY request_session_id, resource_type, request_mode;
GO

Expected takeaway

The demo is structured to show that the same update workload behaves differently depending on whether OPTIMIZED_LOCKING is OFF or ON. Because both environments are identically configured except for the optimized locking setting, any change in observed lock behavior is attributable to that feature.

Final thoughts

SQL Server 2025 Optimized Locking is not just a checkbox feature—it directly changes how you demonstrate concurrency, lock management, and blocking reduction to customers. If you want a clean live demo, the attached billing scripts are perfect because they isolate the feature clearly and make the before-vs-after comparison easy to explain.


Watch the Full Demo

I’ve recorded a complete walkthrough of this setup on my YouTube channel JBSWiki. If you’re a visual learner, go check it out!

👉 Watch here: https://www.youtube.com/watch?v=-XQj5YtnuEY


Thank You,
Vivek Janakiraman

Disclaimer:
The views expressed on this blog are mine alone and do not reflect the views of my company or anyone else. All postings on this blog are provided “AS IS” with no warranties, and confers no rights.

SQL Server 2025 Series : ABORT_QUERY_EXECUTION Explained with Full Live Demo!

If you manage production workloads, you’ve probably seen situations where one problematic query keeps hurting performance for everyone else. In SQL Server 2025, Microsoft highlights ABORT_QUERY_EXECUTION as a Query Store hint that lets administrators block the future execution of a known problematic query without changing application code. That makes it a practical DBA-focused safeguard when you need control quickly.

In this blog, I’ll explain what ABORT_QUERY_EXECUTION is, why it matters, the prerequisites, and then walk through a full live demo using only the code shown below. I’ll also show how to verify whether the hint is configured and how to list blocked queries recorded in Query Store. Microsoft’s product and demo references position this feature as part of the broader query-processing and administrative control improvements in SQL Server 2025.


What is ABORT_QUERY_EXECUTION?

ABORT_QUERY_EXECUTION is a query hint showcased for SQL Server 2025 that can be applied through Query Store hints to block future executions of a specific query. The available Microsoft and demo references consistently describe it as an administrative control for stopping a known bad query from continuing to impact workload stability, while avoiding application code changes.

One important clarification: this feature is described as preventing future executions of the targeted query. The references do not describe it as a general-purpose replacement for terminating a query that is already running.


Why was this introduced?

The purpose is simple and practical. Sometimes a query is known to be expensive or disruptive, but the application team cannot change the code immediately. In that situation, Query Store hints already provide a way to influence query behavior without redeploying code, and ABORT_QUERY_EXECUTION extends that model by allowing the query to be blocked from future execution.

Available references describe this feature as a way to stop rogue or problematic queries from harming the rest of the workload and to improve reliability during incidents. That is what makes it a useful operational safety valve for DBAs.


Prerequisites

Before testing this feature, the main prerequisite is that Query Store must be enabled, because ABORT_QUERY_EXECUTION works through Query Store hints. This requirement is explicitly reflected in the demo references and supporting guidance.

For this walkthrough, you need:

  • SQL Server 2025
  • A database where your demo query can be captured in Query Store
  • Query Store enabled for that database

Full Demo Code

-- ABORT_QUERY_EXECUTION
CREATE OR ALTER procedure [dbo].[usp_procdisplaydata] @Col2 int
as
begin
SELECT top (150000)
[Col1],
[Col2],
[Col3],
[Col4],
[Col5]
FROM dbo.Table1
WHERE [Col2] = @Col2
ORDER BY [Col3];
END
EXEC [dbo].[usp_procdisplaydata] @Col2=2
SELECT TOP (20)
q.query_id,
qt.query_sql_text,
rs.count_executions,
rs.avg_duration,
rs.last_duration
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q
ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p
ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs
ON p.plan_id = rs.plan_id
WHERE qt.query_sql_text LIKE N'%SELECT top (150000)%'
ORDER BY rs.last_execution_time DESC;
GO -- Query Id 38
EXEC sys.sp_query_store_set_hints
@query_id = 38,
@query_hints = N'OPTION (USE HINT (''ABORT_QUERY_EXECUTION''))';
GO
SELECT *
FROM sys.query_store_query_hints
WHERE query_id = 38;
GO
EXEC [dbo].[usp_procdisplaydata] @Col2=2
EXEC sys.sp_query_store_clear_hints
@query_id = 38;
GO
-- Blocked queries in Query Store
SELECT qsh.query_id,
q.query_hash,
qt.query_sql_text
FROM sys.query_store_query_hints AS qsh
INNER JOIN sys.query_store_query AS q
ON qsh.query_id = q.query_id
INNER JOIN sys.query_store_query_text AS qt
ON q.query_text_id = qt.query_text_id
WHERE UPPER(qsh.query_hint_text) LIKE '%ABORT[_]QUERY[_]EXECUTION%'

Step-by-Step Demo Explanation

1) Create the procedure and execute it once

The first part of the demo creates a stored procedure and executes it so the query text is captured in Query Store. That step is important because the feature is implemented through Query Store hints, and you need the corresponding query_id before you can apply the hint.

2) Find the query in Query Store

The next query searches the Query Store catalog views using the text pattern SELECT top (150000) to identify the relevant query and retrieve its query_id. In this demo script, the query id used is 38. The overall approach aligns with the feature’s dependency on Query Store metadata.

3) Apply ABORT_QUERY_EXECUTION

The core configuration step is:

EXEC sys.sp_query_store_set_hints
@query_id = 38,
@query_hints = N'OPTION (USE HINT (''ABORT_QUERY_EXECUTION''))';

This applies the ABORT_QUERY_EXECUTION query hint through Query Store for the specific query. The available demo and product materials explicitly show this feature being implemented through sp_query_store_set_hints.

4) Verify that the hint is configured

The following verification query checks the Query Store catalog view for the configured hint:

SELECT *
FROM sys.query_store_query_hints
WHERE query_id = 38;

This is a practical way to verify whether ABORT_QUERY_EXECUTION has been configured for that query in your database.

5) Execute the procedure again

After the hint is applied, the procedure is executed again so you can validate the configured behavior. Based on the available references, ABORT_QUERY_EXECUTION is designed to block future executions of the targeted query once the hint is in place.

6) Clear the hint

Once testing is complete, the hint is removed using:

EXEC sys.sp_query_store_clear_hints
@query_id = 38;

This completes the full end-to-end lifecycle: identify the query, apply the hint, verify it, test it, and clear it when it is no longer needed.

7) List blocked queries in Query Store

The final query in the script looks for entries in sys.query_store_query_hints where the configured hint text contains ABORT_QUERY_EXECUTION. This is useful when you want to audit or review queries that have been configured with this hint

SELECT qsh.query_id,
q.query_hash,
qt.query_sql_text
FROM sys.query_store_query_hints AS qsh
INNER JOIN sys.query_store_query AS q
ON qsh.query_id = q.query_id
INNER JOIN sys.query_store_query_text AS qt
ON q.query_text_id = qt.query_text_id
WHERE UPPER(qsh.query_hint_text) LIKE '%ABORT[_]QUERY[_]EXECUTION%'

ABORT_QUERY_EXECUTION is one of the more practical DBA-oriented capabilities highlighted in SQL Server 2025. It gives administrators a way to block the future execution of a known problematic query using Query Store hints, without changing application code. That makes it especially valuable when immediate workload protection is needed.


Watch the Full Demo

I’ve recorded a complete walkthrough of this setup on my YouTube channel JBSWiki. If you’re a visual learner, go check it out!

👉 Watch here: https://www.youtube.com/watch?v=SjjC18u-jjM


Thank You,
Vivek Janakiraman

Disclaimer:
The views expressed on this blog are mine alone and do not reflect the views of my company or anyone else. All postings on this blog are provided “AS IS” with no warranties, and confers no rights.