Voici un petit article présentant une fonction non documentée qui permet de localiser l’endroit où est stockée une ligne donnée. Cela peut notamment permettre de situer les autres lignes présentes dans la même page, et qui pourraient subir les effets d’un verrou de la page lors d’une modification.
Pour rappel, les données sont stockées par SQL Server sous forme de pages de 8Ko. Cette taille est fixe; elle permet de comprendre certaines limites au niveau des types de données (varchar(8000), nvarchar(4000), …), en sachant qu’un enregistrement ne peut pas être à cheval sur plusieurs pages (hormis pour certains types qui peuvent être stockés en dehors de la ligne elle-même).
A partir de SQL Server 2008, une nouvelle colonne cachée est disponible pour permettre de situer la page dans laquelle se trouve chacun des enregistrements retournés.
select *,%%physloc%% as [%%physloc%%] from HumanResources.Department
Bon, c’est vrai, vu comme ça, ce n’est pas très parlant…
Les 4 premiers octets correspondent à l’identifiant de page, puis 2 octets pour l’identification du fichier, et enfin les deux derniers octets pour le slot, ce qui correspond en gros au numéro d’enregistrement dans la page.
Heureusement pour décrypter cela, nous avons aussi droit à une magnifique fonction qui nous permet d’avoir un affichage beaucoup plus lisible de la chose.
select *,sys.fn_PhysLocFormatter(%%physloc%%) as [Emplacement] from HumanResources.Department
Nous constatons ici que les divers enregistrement se situent tous sur la page 665 du premier fichier de ma base. Allons donc y jeter un oeil :
dbcc page (8,1,665,1)
PAGE: (1:665) BUFFER: BUF @0x0000000085FDBE80bpage = 0x00000000859FC000 bhash = 0x0000000000000000 bpageno = (1:665) bdbid = 8 breferences = 0 bcputicks = 0 bsampleCount = 0 bUse1 = 16708 bstat = 0xc00009 blog = 0x3212159 bnext = 0x0000000000000000PAGE HEADER: Page @0x00000000859FC000m_pageId = (1:665) m_headerVersion = 1 m_type = 1 m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x220 m_objId (AllocUnitId.idObj) = 135 m_indexId (AllocUnitId.idInd) = 256 Metadata: AllocUnitId = 72057594046775296 Metadata: PartitionId = 72057594044612608 Metadata: IndexId = 1 Metadata: ObjectId = 757577737 m_prevPage = (0:0) m_nextPage = (0:0) pminlen = 14 m_slotCnt = 16 m_freeCnt = 6456 m_freeData = 1704 m_reservedCnt = 0 m_lsn = (41:2945:22) m_xactReserved = 0 m_xdesId = (0:1194) m_ghostRecCnt = 0 m_tornBits = 263213638Allocation StatusGAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED PFS (1:1) = 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED DATA: Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 01000000 00001e92 00000400 †0……….…. Slot 1, Offset 0xbd, Length 93, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 02000000 00001e92 00000400 †0……….…. Slot 2, Offset 0x11a, Length 71, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 03000000 00001e92 00000400 †0……….…. Slot 3, Offset 0x161, Length 79, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 04000000 00001e92 00000400 †0……….…. Slot 4, Offset 0x1b0, Length 83, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 05000000 00001e92 00000400 †0……….…. Slot 5, Offset 0x203, Length 119, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 06000000 00001e92 00000400 †0……….…. Slot 6, Offset 0x27a, Length 69, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 07000000 00001e92 00000400 †0……….…. Slot 7, Offset 0x2bf, Length 85, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 08000000 00001e92 00000400 †0……….…. Slot 8, Offset 0x314, Length 125, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 09000000 00001e92 00000400 †0… ………. Slot 9, Offset 0x391, Length 109, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 0a000000 00001e92 00000400 †0……….…. Slot 10, Offset 0x3fe, Length 135, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 0b000000 00001e92 00000400 †0……….…. Slot 11, Offset 0x485, Length 89, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 0c000000 00001e92 00000400 †0……….…. Slot 12, Offset 0x4de, Length 91, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 0d000000 00001e92 00000400 †0……….…. Slot 13, Offset 0x539, Length 147, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 0e000000 00001e92 00000400 †0……….…. Slot 14, Offset 0x5cc, Length 107, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 0f000000 00001e92 00000400 †0……….…. Slot 15, Offset 0x637, Length 113, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS 0000000000000000: 30000e00 10000000 00001e92 00000400 †0……….…. OFFSET TABLE: Row – Offset |
Nous retrouvons bien ici tous nos enregistrements, comme attendu.
Il est important de bien noter que la page indiquée correspond à l’index qui a été utilisé pour retourner les enregistrements. Il faut donc bien veiller par exemple à ne pas pas espérer retrouver la page de l’index clustered si la requête n’utilise qu’un index non clustered. En effet, chaque index utilise ses propres pages.
Par exemple :
select d.DepartmentID, d.GroupName, sys.fn_PhysLocFormatter(%%physloc%%) as [Emplacement] from HumanResources.Department d with (index(PK_Department_DepartmentID)) order by d.DepartmentID select d.DepartmentID, d.GroupName, sys.fn_PhysLocFormatter(%%physloc%%) as [Emplacement] from HumanResources.Department d with (index(AK_Department_Name)) order by d.DepartmentID
Nous constatons que les pages ne sont pas identiques suivant l’index utilisé.
Voilà donc pour une petite présentation de la colonne cachée %%physlock%%, qui permet de localiser précisément où les données sont rangées. Et le stockage des données étant au coeur de tout moteur, il est toujours intéressant d’avoir le maximum d’informations à son sujet…