Storage Engine In SQL Server Part 3

today we are going to talk about

  • what extent is
  • File header page
  • boot page
  • Page Free Space (PFS)
  • Global allocation map(GAM)
  • Shared global allocation map (SGAM)
  • index allocation map or allocation units (IAM)
  • Differential Changed Map (DIFF)
  • Minimally logged (ML)
  • Index page

Extents in data files

when we create a database SQL server groups 8 pages into logical units called extents

there are two types, mixed disabled by default since SQL server 2016 and before that, they used to use traceflag 1118

this happens when multiple databases use the same extent

and the other one is uniform extents which is the default now

SQL server uses allocation maps to track page and extent usage

GAM(global allocation map) = a bitmap that tracks extent usage

if it has a zero then it is in use

if it has a 1 then it is a free extent

global allocation map are uniform maps that are used for one database

and there are Shared global allocation maps or SGAM pages these are shared between databases

each one of those tracks about 64 000 extents or 4 GBs

so each database has 1 GAM for every 4gbs

so we have the page that stores 8kb

we have extents that store 64 kb

and we have GAM that tracks them, okay, what tracks what objects these extents belong to?

IAM or index allocation maps or allocation units

now IAM pages are created for regular data types that fits in the 8kb separately from LOB row overflow or LOB data files

so, like we saw in the previous blog, if we had varchar(8000) two cols, and one of them had to go outside the 8kb page, we will have one for the in row and one for the overflow

now if we had a text column, then we will have a separate one for LOB data

now the GAM and SGAM and them they are pages essentially, so what tracks them?

the first IAM

and PFS tracks free space

and ML tracks the bulk operations

so we have pages tracked by IAM

we have extents that is tracked by GAM

and we have file page that tracks the page

and we have a boot page that tracks the database.

File header page

it is numbered 0 in a database so if we do the following

USE TestDatabase
CREATE TABLE FILE_PAGE_EXPLORE(
ID INT)
INSERT INTO FILE_PAGE_EXPLORE(ID) VALUES (1)
DBCC IND ('TestDatabase',
           'dbo.file_page_explore',
           -1)
DBCC TRACEON(3604)
DBCC PAGE('TestDatabase'
           ,1,0,3)
           

we get the following

           

PAGE HEADER:

Page @0x0000020B64C34000

m_pageId = (1:0)                    m_headerVersion = 1                 m_type = 15
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x208
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0           Metadata: IndexId = 0               Metadata: ObjectId = 99
m_prevPage = (0:0)                  m_nextPage = (0:0)                  pminlen = 0
m_slotCnt = 1                       m_freeCnt = 6922                    m_freeData = 4784
m_reservedCnt = 0                   m_lsn = (66:98440:17)               m_xactReserved = 0
m_xdesId = (0:0)                    m_ghostRecCnt = 0                   m_tornBits = -1317461446
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           

File Header Data:

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS
Record Size = 1172                  

                       FileIdProp = 1
FileGroupId = 1                     Size = 173056                       MaxSize = 4294967295
Growth = 8192                       Perf = 0                            BackupLsn = (0:0:0)
FirstUpdateLsn = (0:0:0)            OldestRestoredLsn = (0:0:0)         FirstNonloggedUpdateLsn = [NULL]
MinSize = 1024                      Status = 0                          UserShrinkSize = 4294967295
SectorSize = 4096                   MaxLsn = (0:0:0)                    FirstLsn = (0:0:0)
CreateLsn = (0:0:0)                 DifferentialBaseLsn = (0:0:0)       DifferentialBaseGuid = 00000000-0000-0000-0000-000000000000
FileOfflineLsn = (0:0:0)            FileIdGuid = b9c84b85-4b5b-473d-9fb6-764a66043900
RestoreStatus = 0                   RestoreRedoStartLsn = (0:0:0)       RestoreSourceGuid = 00000000-0000-0000-0000-000000000000
HardenedSkipLsn = [NULL]            ReplTxfTruncationLsn = [NULL]       TxfBackupLsn = [NULL]
FstrContainerSize = [NULL]          MaxLsnBranchId = 00000000-0000-0000-0000-000000000000
SecondaryRedoStartLsn = [NULL]      SecondaryDifferentialBaseLsn = [NULL]
ReadOnlyLsn = (0:0:0)               ReadWriteLsn = (0:0:0)              RestoreDifferentialBaseLsn = (0:0:0)
RestoreDifferentialBaseGuid = 00000000-0000-0000-0000-000000000000       
RestorePathOrigin

                 

FCBFileDEK

m_dbeStatusBits = 0                 m_dtCreated = 1900-01-01 00:00:00.000
m_dtLastRegenerated = 1900-01-01 00:00:00.000                            m_dtLastModified = 1900-01-01 00:00:00.000
m_dtLastSet = 1900-01-01 00:00:00.000                                    m_dtOpened = 1900-01-09 00:00:00.000
m_algId = 0                         m_algId = 0                         m_dwBitLen = 0
m_cbThumbprint = 0                  m_rgbThumbprint = 0x                
ProxyFileIdProp = 1                 ForeignRedoLsn = (0:0:0)            ForeignRedoTime = 1900-01-01T00:00:00
ForeignRedoOldestBeginLsn = (0:0:0) 

so we get information related to the file itself where the allocation maps are the type here is 15 = page file header

we see stuff like when was the last restoration, how it was, why it was like that

the size

the growth rate

max size

some old physical hard drive data

logging related information

and in the 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          

we can see the numbers of the other pages that tracks the extents

Boot page

now here instead of tracking the physical file, it tracks the database itself

it is the 9th when first created so:

dbcc page('TestDatabase',1,9,3)

and the output:


PAGE: (1:9)

PAGE HEADER:

Page @0x0000020B477DA000

m_pageId = (1:9)                    m_headerVersion = 1                 m_type = 13
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocU..................................................
1114867035
DB Frag ID = 1                      

Allocation Status

GAM (1:2) = ALLOCATED               SGAM (1:3) = NOT ALLOCATED          PFS (1:1) = 0x64 MIXED_EXT ALLOCATED 100_PCT_FULL
DIFF (1:6) = CHANGED                ML (1:7) = NOT MIN_LOGGED           

Slot 0, Offset 0x60, Length 1786, DumpStyle BYTE

Record Type = PRIMARY_RECORD        Record Attributes =                 Record Size = 1786
dbi_version = 957                   dbi_createVersion = 957             dbi_SEVersion = 0
dbi_dvSplitPoint = 0:0:0 (0x00000000:00000000:0000)                      dbi_dbbackupLSN = 0:0:0 (0x00000000:00000000:0000)
dbi_LastLogBackupTime = 1900-01-01 00:00:00.000                          dbi_nextseqnum = 1900-01-01 00:00:00.000
dbi_status = 0x00010000             dbi_crdate = 2025-02-10 14:08:17.710dbi_dbname = TestDatabase
dbi_dbid.......................
m_guid = 00000000-0000-0000-0000-000000000000                            
dbi_differentialBaseGuid = 00000000-0000-0000-0000-000000000000          dbi_firstSysIndexes = 0001:00000014
dbi_dynamicFileAllocationNewFileID = 0                                   dbi_oldestBackupXactLSN = 0:0:0 (0x00000000:00000000:0000)
dbi_lastLogBackupOldestActiveXactLsn = 0:0:0 (0x00000000:00000000:0000)  dbi_lastLogBackupCheckpointLsn = 0:0:0 (0x00000000:00000000:0000)
dbi_versionChangeLSN = 0:0:0 (0x00000000:00000000:0000)                  dbi_mdUpgStat = 0x0004
dbi_category = 0x0000000000000000   dbi_safetySequence = 0              dbi_dbMirrorId = 00000000-0000-0000-0000-000000000000
dbi_pageUndoLsn = 0:0:0 (0x00000000:00000000:0000)                       dbi_pageUndoState = 0
dbi_disabledSequence = 0            dbi_dbmRedoLsn = 0:0:0 (0x00000000:00000000:0000)
dbi_dbmOldestXactLsn = 0:0:0 (0............................

here we see a similar information and stuff relevant to the compatibility level

containment state

securtiy

checkpoints

logs and backups

partitions

creation date

trace flags and more

Page free space PFS

so(paul randal):

  • it is the second page meaning number 1
  • there is one every 8088 pages
  • type =11
  • as it sounds it tracks free space
  • ghosted records to be deleted
  • full rows
  • every byte represents a page
  • where to insert LOB datatypes or heaps
  • now we know a byte in binary means 8 bits
  • bits 0-2 indicates how full the page is
  • as you can see in the graph 0 means empty,
  • 1 means 1-50% full
  • 2 51-80
  • 3 81-95
  • 4 96-100 percent full
  • 7th bit is unused
  • 6th is it allocated
  • 5th is it a mixed extent
  • 4th is it an IAM page
  • 3rd is there any row to be deleted, is it ghosted

this information is helpful in inserting LOB overflow or LOB data, and in heaps now let’s look at one

dbcc page('TestDatabase',1,1,3)

sample output

PAGE HEADER:

Page @0x0000020B64C32000

m_pageId = (1:1)                    m_headerVersion = 1                 m_type = 11
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0           Metadata: IndexId = 0               Metadata: ObjectId = 99
m_prevPage = (0:0)                  m_nextPage = (0:0)                  pminlen = 0
m_slotCnt = 1                       m_freeCnt = 2                       m_freeData = 8188
m_reservedCnt = 0                   m_lsn = (66:111600:7)               m_xactReserved = 0
m_xdesId = (0:0)                    m_ghostRecCnt = 0                   m_tornBits = -1849234597
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           

PFS: Page Alloc Status  @0x000000ACE1676000

(1:0)        - (1:3)        =     ALLOCATED 100_PCT_FULL                                      
(1:4)        - (1:5)        = NOT ALLOCATED   0_PCT_FULL                                      
(1:6)        - (1:7)        =     ALLOCATED 100_PCT_FULL                                      
(1:8)        -              =     ALLOCATED   0_PCT_FULL                     Mixed Ext        
(1:9)        -              =     ALLOCATE                                    

Global Allocation Map or GAM(uniform = default)

so like we said before, SQL server has pages each 8 of them constitutes extents and they are tracked using GAM pages

each one of them can track 64 thousand extents or 4 gigs

after that a new one will be created

their type = 8

it is the third or number 2 in the file always

it has two bits for each page

if it is allocated = 0

not allocated = we can use it = 1

now we said earlier that an extent is 8 pages

and we know that the first 8 pages are created immediately upon the creation of the file

and these are:

so if we dbcc page the page 2

dbcc page('TestDatabase',1,2,3)

the output:

PAGE HEADER:

Page @0x0000020B64C30000

m_pageId = (1:2)                    m_headerVersion = 1                 m_type = 8
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0           Metadata: IndexId = 0               Metadata: ObjectId = 99
m_prevPage = (0:0)                  m_nextPage = (0:0)                  pminlen = 90
m_slotCnt = 2                       m_freeCnt = 6                       m_freeData = 8182
m_reservedCnt = 0                   m_lsn = (66:111600:21)              m_xactReserved = 0
m_xdesId = (0:0)                    m_ghostRecCnt = 0                   m_tornBits = -766964551
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           

GAM: Header @0x000000ACE1676064 Slot 0, Offset 96

status = 0x0                        

GAM: Extent Alloc Status @0x000000ACE16760C2

(1:0)        - (1:165632)   =     ALLOCATED                              
(1:165640)   - (1:173048)   = NOT ALLOCATED                              

so as you can see all the extents from 1 to 165632 and its extents are all allocated

but after that we have a lot of empty pages

Shared global allocation maps or SGAM(shared)

now here instead of allocating one global allocation map for each database

here it finds for the small database an empty one and makes them share it

this could cause contention in highly concurrent databases especially in tempdb

since everybody might try to allocate stuff to the same page

and they might block each other from using the same resource, key, RID, page, object, database

that is why the default is GAM since it prevents such a problem

now the bits interpretation here is different

if we have at least one extent that yet to be allocated the bit would equal one

but if it did not have any empty extents = 0

now like we saw in the preceding output our file had a SGAM page but it is yet to be allocated

its type is 9

and it is the 4th page or 3

so if we dbcc page it:

dbcc page('TestDatabase',1,3,3)

the output would be like:


PAGE HEADER:

Page @0x0000020B64C2E000

m_pageId = (1:3)                    m_headerVersion = 1                 m_type = 9
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x200
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0           Metadata: IndexId = 0               Metadata: ObjectId = 99
m_prevPage = (0:0)                  m_nextPage = (0:0)                  pminlen = 90
m_slotCnt = 2                       m_freeCnt = 6                       m_freeData = 8182
m_reservedCnt = 0                   m_lsn = (43:51752:10)               m_xactReserved = 0
m_xdesId = (0:0)                    m_ghostRecCnt = 0                   m_tornBits = 1766461006
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           

SGAM: Header @0x000000ACDB276064 Slot 0, Offset 96

status = 0x0                        

SGAM: Extent Alloc Status @0x000000ACDB2760C2

(1:0)        - (1:320)      = NOT ALLOCATED                              
(1:328)      -              =     ALLOCATED                              
(1:336)      - (1:376)      = NOT ALLOCATED                              
(1:384)      -              =     ALLOCATED                              
(1:392)      - (1:456)      = NOT ALLOCATED                              
(1:464)      -              =     ALLOCATED                              
(1:472)      - (1:536)      = NOT ALLOCATED                              
(1:544)      -              =     ALLOCATED                              
(1:552)      - (1:640)      = NOT ALLOCATED                              
(1:648)      -              =     ALLOCATED                              
(1:656)      - (1:720)      = NOT ALLOCATED                              
(1:728)      -              =     ALLOCATED                              
(1:736)      - (1:173048)   = NOT ALLOCATED                              

so it found the pages that had empty spaces but, it would not be used since our default is that these are allocated to the database and there is no need to use them since storage is plenty and way too cheaper than memory or CPU or any other resource

Index allocation map or IAM or allocation units

now we track extents using GAM or SGAM, so we know if one of them is being used

but how do we know who is using them, and what objects they belong to?

IAM

now each one of them tracks a GAM interval or a 4GBs

and we could have multiple tables etc. so we would need as much

and we could have multiple indexes

and we could have different datatypes like row_overflow LOB data or in_row each needs its own IAM page

its type is 10 and we could do the following to get the page number

create table alloaction_unit_explore(
col1 int)
insert into allocation_unit_explore(col1) values (1)
dbcc ind('TestDatabase','dbo.allocation_unit_explore',-1)
dbcc traceon(3604)
dbcc page('TestDatabase',1,735,3)

735 is our page so the output was like


PAGE: (1:735)

PAGE HEADER:

Page @0x0000020BEBBB6000

m_pageId = (1:735)                  m_headerVersion = 1                 m_type = 10
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 258   m_indexId (AllocUnitId.idInd) = 256 Metadata: AllocUnitId = 72057594054836224
Metadata: PartitionId = 72057594047561728                                Metadata: IndexId = 0
Metadata: ObjectId = 1765581328     m_prevPage = (0:0)                  m_nextPage = (0:0)
pminlen = 90                        m_slotCnt = 2                       m_freeCnt = 6
m_freeData = 8182                   m_reservedCnt = 0                   m_lsn = (66:111696:15)
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) = ALLOCATED              PFS (1:1) = 0x70 IAM_PG MIXED_EXT ALLOCATED   0_PCT_FULL
DIFF (1:6) = CHANGED                ML (1:7) = NOT MIN_LOGGED           

IAM: Header @0x000000ACD57F6064 Slot 0, Offset 96

sequenceNumber = 0                  status = 0x0                        objectId = 0
indexId = 0                         page_count = 0                      start_pg = (1:0)

IAM: Single Page Allocations @0x000000ACD57F608E

Slot 0 = (0:0)                      Slot 1 = (0:0)                      Slot 2 = (0:0)
Slot 3 = (0:0)                      Slot 4 = (0:0)                      Slot 5 = (0:0)
Slot 6 = (0:0)                      Slot 7 = (0:0)                      

IAM: Extent Alloc Status Slot 1 @0x000000ACD57F60C2

(1:0)        - (1:165632)   = NOT ALLOCATED                              
(1:165640)   -              =     ALLOCATED                              
(1:165648)   - (1:173048)   = NOT ALLOCATED                              

now first we see that our type is 10 which what we expected

fore more about index id check out paul randal

we don’t have next or previous IAM pages since our table was small enough to be allocated by just one

we have two slots slot 0 for:

IAM page header

it has,

  • sequence number: where is it in the IAM chain is it the first the last
  • status = unused
  • objectid
  • indexID: more on it in randal’s blog
  • page_count: unused
  • start page: the first page in this GAM interval that is being tracked by our IAM page
  • then after that we have the array that says IAM: Single Page Allocations, now this tracks which pages has been a mixed allocation extent it is only present in the first page

after that we have our slot 1 or the slot for the bitmap, it tracks the allocation itself

Combining GAM and SGAM

now we have combinations adopted from paul randal

feel free to check them out

Data page

most of the pages that we explored in the previous blogs are data pages

feel free to check them out

Index page

now we will dive into more details on this in upcoming blogs.

Minimally logged (ML) page

it is type 17, and it is page 7

it holds information about the transactions that used bulk mode

for example:

create table ml_explore(
col1 int)
dbcc traceon(3604)
dbcc page('TestDatabase',1,7,3)

the output would be:


PAGE HEADER:

Page @0x0000020B64C26000

m_pageId = (1:7)                    m_headerVersion = 1                 m_type = 17
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0           Metadata: IndexId = 0               Metadata: ObjectId = 99
m_prevPage = (0:0)                  m_nextPage = (0:0)                  pminlen = 90
m_slotCnt = 2                       m_freeCnt = 6                       m_freeData = 8182
m_reservedCnt = 0                   m_lsn = (0:0:1)                     m_xactReserved = 0
m_xdesId = (0:0)                    m_ghostRecCnt = 0                   m_tornBits = 0
DB Frag ID = 1                      

ML_MAP: Header @0x000000ACD15F6064 Slot 0, Offset 96

status = 0x0                        

ML_MAP: Extent Alloc Status @0x000000ACD15F60C2

(1:0)        - (1:173048)   = NOT MIN_LOGGED                             

now since we did not bulk anything we won’t see anything

Differential Changed Map (DIFF)

now here it shows us which extents has been changed since the last full or differential backup

for example:

create table explore_diff(
col1 int )
insert into explore_diff(col1)values(1)
dbcc traceon(3604)
dbcc page('TestDatabase',1,6,3)

the output was like:


PAGE HEADER:

Page @0x0000020B64C20000

m_pageId = (1:6)                    m_headerVersion = 1                 m_type = 16
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 99    m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 6488064
Metadata: PartitionId = 0           Metadata: IndexId = 0               Metadata: ObjectId = 99
m_prevPage = (0:0)                  m_nextPage = (0:0)                  pminlen = 90
m_slotCnt = 2                       m_freeCnt = 6                       m_freeData = 8182
m_reservedCnt = 0                   m_lsn = (67:6904:23)                m_xactReserved = 0
m_xdesId = (0:0)                    m_ghostRecCnt = 0                   m_tornBits = 994682355
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           

DIFF_MAP: Header @0x000000ACCA7F6064 Slot 0, Offset 96

status = 0x0                        

DIFF_MAP: Extent Alloc Status @0x000000ACCA7F60C2

(1:0)        - (1:165704)   =     CHANGED                                
(1:165712)   - (1:169840)   = NOT CHANGED                                
(1:169848)   -              =     CHANGED                                
(1:169856)   - (1:173048)   = NOT CHANGED                                

so the type is 16 and it is number 6 and it shows us which stuff has been changed as you can see in slot 0

now we are done with pages, see you in the next blog

Leave a Reply

Your email address will not be published. Required fields are marked *