22.11.25

Как вычисляются идентификаторы единиц распределения?

Автор: Paul Randal, Inside the Storage Engine: How are allocation unit IDs calculated?

Давненько я не писал о чистой «внутренней кухне», но меня время от времени спрашивают, как вычисляется идентификатор единицы распределения по полям m_objId и m_indexId, которые хранятся в заголовке каждой страницы.

Когда DBCC PAGE выводит содержимое заголовка страницы, она выполняет необходимые вычисления и обращения к метаданным, чтобы показать идентификатор единицы распределения, идентификатор секции (partition), идентификатор табличного объекта и идентификатор индекса.

По сути, всё, что в выводе DBCC PAGE ниже помечено префиксом «Metadata:», на самой странице НЕ хранится:

Page @0x00000004ED8A2000
m_pageId = (1:445)                  m_headerVersion = 1                 m_type = 1
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0xa000
m_objId (AllocUnitId.idObj) = 97    m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594044284928
Metadata: PartitionId = 72057594039304192                                Metadata: IndexId = 0
Metadata: ObjectId = 599673184      m_prevPage = (0:0)                  m_nextPage = (0:0)
pminlen = 8                         m_slotCnt = 1                       m_freeCnt = 8069
m_freeData = 121                    m_reservedCnt = 0                   m_lsn = (225:443:22)
m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0
m_tornBits = 0                      DB Frag ID = 1

Формула такова:

  • Возьмите m_indexId и выполните сдвиг влево на 48 бит, получив значение A.
  • Возьмите m_objId и выполните сдвиг влево на 16 бит, получив значение B.
  • AllocUnitId = A | B  (где | — побитовое ИЛИ).

Используя страницу выше:

  • A = 256 << 48 = 72057594037927936
  • B = 97 << 16 = 6356992
  • AllocUnitId = 72057594044284928

То же самое можно сделать в SQL Server с помощью функции POWER, поскольку сдвиг влево на X бит эквивалентен умножению на 2 в степени X:

SELECT 256 * CONVERT (BIGINT, POWER (2.0, 48)) | 97 * CONVERT (BIGINT, POWER (2.0, 16));
GO

Затем можно выполнить соответствующие поиски в sys.system_internals_allocation_units и sys.partitions примерно так:

SELECT
    [a].[container_id] AS [Partition ID],
    [p].[object_id]    AS [Object ID],
    [p].[index_id]     AS [Index ID]
FROM sys.system_internals_allocation_units AS [a]
JOIN sys.partitions AS [p]
    ON [p].[partition_id] = [a].[container_id]
WHERE
    [a].[allocation_unit_id] = 72057594044284928;
GO
Partition ID         Object ID   Index ID
-------------------- ----------- -----------
72057594039304192    599673184   0

Как видите, значения совпадают с выводом DBCC PAGE.

Чтобы преобразовать из идентификатора единицы распределения к тому, что вы увидите в выводе DBCC PAGE:

  • m_indexId = AllocUnitId >> 48
  • m_objId = (AllocUnitId – (m_indexId << 48)) >> 16

T-SQL для этого использует вычисления с плавающей запятой, так как нам нужен обратный коэффициент к POWER:

DECLARE @alloc BIGINT = 72057594044284928;
DECLARE @index BIGINT;
SELECT @index =
    CONVERT (BIGINT,
        CONVERT (FLOAT, @alloc)
            * (1 / POWER (2.0, 48)) -- right shift, reciprocal of left shift
    );
SELECT
    CONVERT (BIGINT,
        CONVERT (FLOAT, @alloc - (@index * CONVERT (BIGINT, POWER (2.0, 48))))
            * (1 / POWER (2.0, 16)) -- right shift, reciprocal of left shift
    ) AS [m_objId],
    @index AS [m_indexId];
GO
m_objId              m_indexId
-------------------- --------------------
97                   256

Этот приём и код могут пригодиться, например, при программном анализе повреждённой базы данных, которую DBCC CHECKDB не удаётся обработать, чтобы в крайнем случае извлечь из неё данные.



Комментариев нет:

Отправить комментарий