SQL Server 2025 Optimized Locking: TID internals
Optimized Locking è una funzionalità dell’Engine di SQL Server 2025, è stata implementata per ridurre l’utilizzo della memoria impiegata per la gestione dei lock, ridurre il fenomeno noto con il nome di lock escalation e aumentare la concorrenza dei carichi di lavoro. Nei precedenti articoli di questa serie: SQL Server 2025 Optimized Locking e Optimized Locking Internals: Gestione dei lock in aggiornamenti concorrenti abbiamo descritto rispettivamente il funzionamento generale di Optimized Locking e come avviene la gestione dei lock negli aggiornamenti concorrenti.
In questo articolo scopriremo come consultare il Transaction ID che ha modificato una riga del database.
Cosa è il Transaction ID?
Il Transaction ID (TID) è un identificatore univoco di transazione. Quando è attivo un livello di isolamento basato sul versioning delle righe o quando Accelerated Database Recovery (ADR) è abilitato, ogni riga del database contiene internamente un identificatore di transazione. Il TID viene memorizzato su disco nei 14 byte aggiuntivi che vengono associati alla riga quando si abilità RCSI o ADR (ad esempio). Ogni transazione che modifica una riga etichetta quella riga con il suo TID, quindi ogni riga del database è etichettata con l’ultimo TID che l’ha modificata.
Come posso consultare il Transaction ID di una riga?
Attualmente, l’unico modo per leggere il Transaction ID di una riga è attraverso il comando DBCC PAGE e di seguito vedremo come fare. Consideriamo la tabella dbo.TelemetryPacket, per il database di esempio OptimizedLocking, con lo schema definito nel seguente frammento di codice T-SQL.
DROP TABLE IF EXISTS dbo.TelemetryPacket;
CREATE TABLE dbo.TelemetryPacket
(
PacketID INT IDENTITY(1, 1)
,Device CHAR(8000) DEFAULT ('Something')
);
GO
Per semplicità, lo schema della tabella è progettato in modo che ogni riga occupi esattamente una pagina di dati, inseriamo quindi tre righe con valori di default nella tabella dbo.TelemetryPacket; si noti, in un’unica transazione.
BEGIN TRANSACTION
INSERT INTO dbo.TelemetryPacket DEFAULT VALUES;
INSERT INTO dbo.TelemetryPacket DEFAULT VALUES;
INSERT INTO dbo.TelemetryPacket DEFAULT VALUES;
COMMIT
GO
Esploriamo ora il contenuto della tabella dbo.TelemetryPacket arricchito dalla colonna PageId che mostra il risultato della funzione non documentata sys.fn_PhysLocFormatter, che utilizziamo per correlare le righe restituite dalla SELECT con la loro posizione fisica su disco.
SELECT
*
,PageId = sys.fn_PhysLocFormatter(%%physloc%%)
FROM
dbo.TelemetryPacket;
L’output che si ottiene è il seguente.
| PageId | PacketID | Device |
| --- | --- | --- |
| (1:2456:0) | 1 | Something |
| (1:2457:0) | 2 | Something |
| (1:2458:0) | 3 | Something |
I valori visualizzati nella colonna PageId rappresentano la posizione fisica dei dati. Osserviamo la riga con PacketID uguale a 1. Il valore (1:2456:0) nella colonna PageId è da intendersi composto da tre parti separate dal separatore “:” e questo è significato dei singoli valori:
-
1 è l’identificativo numerico del file (file number) del database in cui si trova la pagina
-
2456 è la pagina all’interno del file 1 del database
-
0 è il numero di slot
Ogni riga della tabella è stata inserita in una pagina distinta, la prima riga nella pagina 2456, la seconda riga nella pagina 2457 e la terza riga nella pagina 2458. Utilizziamo ora il comando DBCC PAGE, come segue, per esaminare il TID della pagina 2456 che contiene il record con PacketID uguale a 1.
-- (1:2456:0)
DBCC PAGE ('OptimizedLocking', 1, 2456, 3);
L’output che si ottiene, semplificato con i soli dati salienti, è il seguente.
PAGE: (1:2456)
BUFFER:
BUF @0x000002292072CA80
bpage = 0x000002277583C000 bPmmpage = 0x0000000000000000 bsort_r_nextbP = 0x0000000000000000
bsort_r_prevbP = 0x0000000000000000 bhash = 0x0000000000000000 bpageno = (1:2456)
bpart = 1 bstat = 0xb breferences = 0
berrcode = 0 bUse1 = 21321 bstat2 = 0x0
blog = 0x1cc bsampleCount = 0 bIoCount = 0
resPoolId = 0 bcputicks = 0 bReadMicroSec = 773
bDirtyPendingCount = 0 bDirtyContext = 0x000002277AD71880 bDbPageBroker = 0x0000000000000000
bdbid = 9 bpru = 0x0000022773120040
PAGE HEADER:
Page @0x000002277583C000
m_pageId = (1:2456) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0xa000
m_objId (AllocUnitId.idObj) = 245 m_indexId (AllocUnitId.idInd) = 256 Metadata: AllocUnitId = 72057594053984256
Metadata: PartitionId = 72057594047234048 Metadata: IndexId = 0
Metadata: ObjectId = 1525580473 m_prevPage = (0:0) m_nextPage = (0:0)
pminlen = 8008 m_slotCnt = 1 m_freeCnt = 69
m_freeData = 8121 m_reservedCnt = 0 m_lsn = (44:1112:24)
m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0
m_tornBits = 0 DB Frag ID = 1
Allocation Status
GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x44 ALLOCATED 100_PCT_FULL
DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED
Slot 0 Offset 0x60 Length 8025
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VERSIONING_INFO
Record Size = 8025
Memory Dump @0x00000078659F8060
0000000000000000: 5000481f 01000000 536f6d65 7468696e 67202020 P.H.....Something
0000000000000014: 20202020 20202020 20202020 20202020 20202020
0000000000001F2C: 20202020 20202020 20202020 20202020 20202020
0000000000001F40: 20202020 20202020 02000001 00000003 00fcffd9 .........üÿÙ
0000000000001F54: 03000000 00
Version Information =
Transaction Timestamp: 985
Version Pointer: Null
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
PacketID = 1
Slot 0 Column 2 Offset 0x8 Length 8000 Length (physical) 8000
Device = Something…
Il valore dell’identificatore univoco della transazione (TID) che ha modificato la riga con PacketID uguale a 1 si trova nella sezione Version Information, attributo Transaction Timestamp e per questa riga corrisponde al valore 985. In questo preciso scenario, il TID 985 sarà presente anche sulle altre righe della tabella perché i comandi di INSERT sono stati eseguiti tutti all’interno della stessa transazione con questo script T-SQL.
BEGIN TRANSACTION;
INSERT INTO dbo.TelemetryPacket DEFAULT VALUES;
INSERT INTO dbo.TelemetryPacket DEFAULT VALUES;
INSERT INTO dbo.TelemetryPacket DEFAULT VALUES;
COMMIT;
GO
Il TID 985 rappresenta quindi, in questo momento, l’identificatore della transazione che ha inserito le righe, ogni successiva variazione ai record della tabella aggiornerà il TID con quello della relativa transazione.
Conclusione
La possibilità di ispezionare il Transaction ID (TID) di una riga offre una prospettiva più profonda su come SQL Server 2025 gestisce versioning, concorrenza e meccanismi interni dell’Engine. Sebbene al momento l’unico modo per leggere il TID sia tramite DBCC PAGE, questa informazione resta estremamente utile per comprendere il comportamento dell’Engine. Il TID permette infatti di collegare direttamente una riga alla transazione che l’ha modificata e aiuta a interpretare correttamente fenomeni come dipendenze tra transazioni, wait types dovuti ai lock XACT e propagazione delle versioni. In sintesi, conoscere dove si trova e come leggere il TID è un passo fondamentale per chi vuole comprendere gli internals del nuovo modello di locking introdotto con SQL Server 2025.