SQL Server 2022 — How can SHARED_TRANSACTION_WORKSPACE lock ownership be independently verified? We're valida

Russell Services 0 Reputation points
2026-10-29T01:58:28.31+00:00

Subject: SQL Server 2022 — How can SHARED_TRANSACTION_WORKSPACE lock ownership be independently verified?

We're validating an isolated synthetic database with a multi-session concurrency test.

SQL Server reports a granted database shared lock with these properties:

resource_type: DATABASE

request_mode: S

request_status: GRANTED

request_owner_type: SHARED_TRANSACTION_WORKSPACE

request_owner_id: 0

Our safety requirement is to independently establish that the lock belongs to an authorized transaction, rather than assuming that a recognized session owns it.

We have not established an unambiguous mapping through the documented transaction DMVs. Microsoft's documentation also identifies request_owner_lockspace_id as unsupported.

Question: Is there a documented, supported SQL Server 2022 mechanism for independently attributing this workspace lock to a specific transaction?

If not, is there a supported alternative for verifying transaction ownership and isolation without weakening the test's containment guarantees?

Please provide documentation or explain the applicable SQL Server limitations.

We are not requesting a way to bypass the ownership checks.

SQL Server | SQL Server Transact-SQL
0 comments No comments

3 answers

Sort by: Newest
  1. Paul Thomas 80 Reputation points
    2026-10-30T06:01:23.4+00:00

    Following up on your comment under Erland's answer about the QUERY STORE ASYNC FLUSH locks: yes, those locks belong to SQL Server itself. I reproduced them on SQL Server 2022 (16.0.4305.5, Developer edition in Docker). I created a database with Query Store on, ran a small insert workload, and polled sys.dm_tran_locks every 50 ms.

    What I saw while Query Store flushed its data

    • Sessions with the command QUERY STORE ASYNC FLUSH (and in other polls, BRKR TASK) held a DATABASE S lock with request_owner_type = SHARED_TRANSACTION_WORKSPACE and request_owner_id = 0, exactly like the lock in your original question.
    • The same sessions also held TRANSACTION-owned locks: DATABASE (subtype QDS), METADATA (QDS_STATEMENT_STABILITY, QDS_QUERY_TEXT_STABILITY), OBJECT, PAGE, KEY and RID.
    • When one of those transaction IDs could be found in sys.dm_tran_active_transactions, it was named QDS base transaction or QDS nested transaction.
    • None of these transactions ever appeared in sys.dm_tran_session_transactions. That's why you can't map them through the session transaction DMVs: they aren't user transactions bound to a session.
    • All of these sessions had is_user_process = 0 in sys.dm_exec_sessions. Their login_name was sa, so the login doesn't tell them apart.
    • CHECKPOINT and TASK MANAGER background sessions also briefly held locks in the database, including the same SHARED_TRANSACTION_WORKSPACE lock.
    • The session IDs of these background tasks varied (2, 5, 36, 57, 76), so the old rule "session_id 50 or lower is system" doesn't hold.

    For comparison, a user session running BEGIN TRAN; UPDATE ... showed the same session-level SHARED_TRANSACTION_WORKSPACE lock (owner ID 0, as Erland explained). Its TRANSACTION locks had a request_owner_id equal to CURRENT_TRANSACTION_ID() and to the transaction in sys.dm_tran_session_transactions.

    A verification approach that separates the two

    The supported, documented discriminator is sys.dm_exec_sessions.is_user_process, which is 0 for system sessions. The check that worked for me:

    • is_user_process = 0: SQL Server background task. Don't treat it as an unauthorized transaction.
    • is_user_process = 1: the session must be on your allowlist. Every TRANSACTION-owned lock must have a request_owner_id that one of your test sessions registered with CURRENT_TRANSACTION_ID() and that appears in sys.dm_tran_session_transactions for that session.
    • SHARED_TRANSACTION_WORKSPACE locks: attribute them to the session, not a transaction.

    Here's the query I used (replace SynthDb with your database name):Following up on your comment under Erland's answer about the QUERY STORE ASYNC FLUSH locks: yes, those locks belong to SQL Server itself. I reproduced them on SQL Server 2022 (16.0.4305.5, Developer edition in Docker). I created a database with Query Store on, ran a small insert workload, and polled sys.dm_tran_locks every 50 ms.

    What I saw while Query Store flushed its data

    • Sessions with the command QUERY STORE ASYNC FLUSH (and in other polls, BRKR TASK) held a DATABASE S lock with request_owner_type = SHARED_TRANSACTION_WORKSPACE and request_owner_id = 0, exactly like the lock in your original question.
    • The same sessions also held TRANSACTION-owned locks: DATABASE (subtype QDS), METADATA (QDS_STATEMENT_STABILITY, QDS_QUERY_TEXT_STABILITY), OBJECT, PAGE, KEY and RID.
    • When one of those transaction IDs could be found in sys.dm_tran_active_transactions, it was named QDS base transaction or QDS nested transaction.
    • None of these transactions ever appeared in sys.dm_tran_session_transactions. That's why you can't map them through the session transaction DMVs: they aren't user transactions bound to a session.
    • All of these sessions had is_user_process = 0 in sys.dm_exec_sessions. Their login_name was sa, so the login doesn't tell them apart.
    • CHECKPOINT and TASK MANAGER background sessions also briefly held locks in the database, including the same SHARED_TRANSACTION_WORKSPACE lock.
    • The session IDs of these background tasks varied (2, 5, 36, 57, 76), so the old rule "session_id 50 or lower is system" doesn't hold.

    For comparison, a user session running BEGIN TRAN; UPDATE ... showed the same session-level SHARED_TRANSACTION_WORKSPACE lock (owner ID 0, as Erland explained). Its TRANSACTION locks had a request_owner_id equal to CURRENT_TRANSACTION_ID() and to the transaction in sys.dm_tran_session_transactions.

    A verification approach that separates the two

    The supported, documented discriminator is sys.dm_exec_sessions.is_user_process, which is 0 for system sessions. The check that worked for me:

    • is_user_process = 0: SQL Server background task. Don't treat it as an unauthorized transaction.
    • is_user_process = 1: the session must be on your allowlist. Every TRANSACTION-owned lock must have a request_owner_id that one of your test sessions registered with CURRENT_TRANSACTION_ID() and that appears in sys.dm_tran_session_transactions for that session.
    • SHARED_TRANSACTION_WORKSPACE locks: attribute them to the session, not a transaction.

    Here's the query I used (replace SynthDb with your database name):

    SELECT l.request_session_id,
           s.is_user_process,
           r.command,
           l.resource_type,
           l.request_mode,
           l.request_owner_type,
           l.request_owner_id,
           at.name AS transaction_name,
           st.transaction_id AS session_transaction_id,
           CASE
               WHEN s.is_user_process = 0 THEN 'SQL Server background task'
               WHEN l.request_owner_type = 'TRANSACTION' AND st.transaction_id IS NULL THEN 'CHECK: user lock with unknown transaction'
               ELSE 'User session: check session/transaction against allowlist'
           END AS classification
    FROM sys.dm_tran_locks AS l
    JOIN sys.dm_exec_sessions AS s ON s.session_id = l.request_session_id
    LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = l.request_session_id
    LEFT JOIN sys.dm_tran_active_transactions AS at
           ON l.request_owner_type = 'TRANSACTION' AND at.transaction_id = l.request_owner_id
    LEFT JOIN sys.dm_tran_session_transactions AS st
           ON st.session_id = l.request_session_id AND st.transaction_id = l.request_owner_id
    WHERE l.resource_database_id = DB_ID(N'SynthDb');
    
    

    During a flush, this showed the QDS nested transaction locks as "SQL Server background task" and my test session as "User session".

    Don't rely on command or the QDS transaction names. They're useful for logging, but they're descriptive values, not a documented contract, and I saw the same Query Store work under two different command names.

    If you'd rather not see these locks at all

    Query Store is on by default for new databases in SQL Server 2022, and that includes LocalDB. If your synthetic test database doesn't need it, turning it off removes this source of background locks:

    ALTER DATABASE SynthDb SET QUERY_STORE = OFF; 
    
    

    Other background tasks such as checkpoint can still take short locks, so keep the is_user_process rule either way.

    Let me know how it goes.

    Was this answer helpful?

    1 person found this answer helpful.

  2. Erland Sommarskog 138.6K Reputation points MVP Volunteer Moderator
    2026-10-29T08:10:49.3533333+00:00

    Is there a documented, supported SQL Server 2022 mechanism for independently attributing this workspace lock to a specific transaction?

    No, because this lock is not related to any transaction at all, but it is a session-level lock. When a process accesses a database, it needs a lock on the database to report that it is there. This serves to block other processes that may want to perform actions that require exclusive access to the database, for instance drop the database or change the setting for READ_COMMITTED_SNAPSHOT.

    Was this answer helpful?


  3. AI answer

    2026-10-29T02:00:04.8966667+00:00

    There is no documented, supported snapshot join that maps a SHARED_TRANSACTION_WORKSPACE row in sys.dm_tran_locks to one specific transaction; exact attribution must instead be captured prospectively when the lock is acquired, or containment must be proven independently at the transaction/session boundary.

    Why the live DMV row cannot provide that proof

    For request_owner_type = TRANSACTION, request_owner_id contains the transaction ID. That contract is not documented for SHARED_TRANSACTION_WORKSPACE; workspaces are internal structures used to hold locks for enlisted sessions. In addition:

    • request_owner_lockspace_id is informational, unsupported, and not guaranteed for future compatibility.
    • request_session_id identifies the session currently owning the request, but can change for bound and distributed transactions.
    • sys.dm_tran_locks exposes mutable live lock-manager state and contains no history.
    • lock_owner_address is documented only for correlation with sys.dm_os_waiting_tasks.resource_address, not as a transaction identifier.

    Therefore, request_owner_id = 0, the session ID, lockspace ID, or internal memory address cannot be used as a supported independent workspace-to-transaction identity chain ^1^.

    Exact prospective attribution

    The documented Lock:Acquired SQL Trace event records these values in the same acquisition event:

    • OwnerID = 4 for SHARED_TRANSACTION_WORKSPACE
    • TransactionID
    • SPID and RequestID
    • DatabaseID, resource Type, lock Mode, login identity, and event sequence

    This can establish the transaction ID associated with the workspace lock at acquisition time, provided capture starts before the test and the required event is successfully retained ^2^.

    In SQL Server Profiler:

    1. Create a trace before any test session starts.
    2. Select Locks → Lock:Acquired and Locks → Lock:Released.
    3. Show at least OwnerID, TransactionID, SPID, RequestID, DatabaseID, Type, Mode, EventSequence, LoginName, and SessionLoginName.
    4. Filter DatabaseID to the synthetic database.
    5. Save to a trace file rather than relying only on the live display.
    6. Reject the test run if capture started late, stopped early, or did not contain the expected acquisition and release records.
    7. Match OwnerID = 4, Type = 2 (DATABASE), and Mode = 3 (S), then compare its TransactionID with the transaction IDs registered by the test harness.

    SQL Trace and Profiler are deprecated but remain documented for SQL Server; Microsoft recommends Extended Events for new monitoring ^3^.

    Do not assume that sqlserver.lock_acquired exposes the SQL Trace columns under identical names. Before substituting Extended Events, inspect the installed SQL Server 2022 event metadata:

    SELECT
        o.name AS event_name,
        c.name AS column_name,
        c.column_type,
        c.type_name,
        c.description
    FROM sys.dm_xe_objects AS o
    JOIN sys.dm_xe_object_columns AS c
      ON c.object_name = o.name
     AND c.object_package_guid = o.package_guid
    WHERE o.object_type = N'event'
      AND o.name IN (N'lock_acquired', N'lock_released')
    ORDER BY o.name, c.column_type, c.name;
    

    Only use Extended Events for the ownership assertion if this metadata demonstrates that the installed events expose the owner category, transaction ID, resource identity, and lock mode needed by the test. Extended Events supports event-file targets and is the recommended tracing platform, but the available event payload must be verified rather than inferred from SQL Trace ^4^.

    Supported containment verification without lock attribution

    If prospective lock-event capture is not acceptable, structure the test so authorization is established independently of that workspace row:

    1. At transaction start, have each authorized connection register its transaction ID:
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION;
    
    SELECT
        @@SPID AS session_id,
        CURRENT_TRANSACTION_ID() AS transaction_id,
        @@TRANCOUNT AS transaction_count;
    

    CURRENT_TRANSACTION_ID() returns the current session’s transaction ID ^5^.

    1. From the monitoring connection, enumerate all enlisted sessions and transactions:
    SELECT
        st.session_id,
        st.transaction_id,
        st.is_user_transaction,
        st.is_local,
        st.is_enlisted,
        st.is_bound,
        st.open_transaction_count,
        es.transaction_isolation_level
    FROM sys.dm_tran_session_transactions AS st
    JOIN sys.dm_exec_sessions AS es
      ON es.session_id = st.session_id;
    

    The transaction DMV explicitly supports one transaction under multiple sessions and multiple transactions under one MARS session ^6^.

    1. Fail closed unless every active user transaction and enlisted session belongs to the harness’s allowlist.
    2. Ensure no unregistered connection can access the isolated database during the test.
    3. Validate isolation behavior using controlled conflicting reads/writes, not only sys.dm_exec_sessions.transaction_isolation_level. That column reports the session setting; statement-level hints can alter individual operations ^7^.

    This proves the authorized transaction set and containment conditions, but it does not prove that the particular live workspace-lock row belongs to one member of that set.

    For SQL Server 2022, querying the server-wide DMVs requires VIEW SERVER PERFORMANCE STATE. Creating or altering a server Extended Events session requires the corresponding CREATE ANY EVENT SESSION or ALTER ANY EVENT SESSION permission ^8^.


    References

    1. sys.dm_tran_locks (Transact-SQL)
    2. Lock:Acquired Event Class
    3. SQL Trace
    4. Quickstart: Extended Events
    5. CURRENT_TRANSACTION_ID (Transact-SQL)
    6. sys.dm_tran_session_transactions (Transact-SQL)
    7. Snapshot isolation in SQL Server
    8. Extended Events overview
    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.