Давненько я не писал о чистой «внутренней кухне», но меня время от времени спрашивают, как вычисляется идентификатор единицы распределения по полям 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 >> 48m_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 не удаётся обработать, чтобы в крайнем случае извлечь из неё данные.

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