SQL.RU
 client/server technologies
 Главная | Документация | Статьи | Книги | Форум | Блоги | Опросы | Гостевая | Рассылка | Работа | Поиск | FAQ |

Ковыряем DBCC PAGE

ПУБЛИКАЦИИ  

По материалам статьи Paul Randal: Poking about with DBCC PAGE (Part 1 of ?)
Перевод Ирины Наумовой

На свою статью How to use DBCC PAGE я получил множество откликов с просьбой рассказать побольше об использовании DBCC PAGE. Я планировал подождать с этим и сначала написать больше про DBCC CHECKDB, потом перейти к разным примерам ошибок, и только потом снова начать писать про DBCC PAGE. Однако, друг сделал мне предложение, от которого я не смог отказаться, так что я начинаю эту серию статей сейчас.
Начнём с того, как заполучить запись напрямую. Да, я знаю, что опция 3 команды DBCC PAGE позволяет это сделать - но как это происходит? Иногда невозможно получить метаданные таблицы, что делает невозможным использование DBCC PAGE с опцией 3. В таких случаях Вам нужно будет сделать это самим. К тому же имеется множество любопытных людей, которые любят покопаться в битах и байтах. Для них получать данные вручную - это забава (если они увлечены этим также как я).
Для экспериментов я создал на SQL Server 2005 базу данных dbccpagetest, которую я заархивировал и присоединил к статье. Таким образом, Вы можете использовать те же самые данные и страницы что и я и не заниматься скучной работой со страницами, используемыми на PFS страницах (то, что я объяснял в статье How to use DBCC PAGE). Вы не сможете присоединить эту базу к SQL Server 2000, но всегда можно скачать SQL Server 2005 Express Edition и проводить эксперименты на нем. Разархивируйте файлы и присоедините их, используя инструкцию CREATE DATABASE ... FOR ATTACH:

CREATE DATABASE dbccpagetest ON (FILENAME='C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\dbccpagetest.mdf') FOR ATTACH; GO

Обратите внимание, что, если не менять имена файлов во время создания, Вам понадобится лишь определить первичный файл в инструкции CREATE DATABASE. Это здорово.
В базе данных имеется одна таблица, в которой две строки, она создана с помощью следующего кода на T-SQL:

CREATE TABLE example (       destination VARCHAR(100),       activity VARCHAR(100),       duration INT); GO INSERT INTO example VALUES ('Banff', 'sightseeing', 5); INSERT INTO example VALUES ('Chicago', 'sailing', 4); GO

Страница данных - это страница (1:152). Получая дамп этой страницы при помощи опции 3, мы видим результат, содержащий следующую информацию:

Slot 0 Offset 0x60 Length 33 Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Memory Dump @0x44DFC060 00000000: 30000800 05000000 0300f802 00160021 †0..............! 00000010: 0042616e 66667369 67687473 6565696e †.Banffsightseein 00000020: 67†††††††††††††††††††††††††††††††††††g Slot 0 Column 0 Offset 0x11 Length 5 destination = Banff Slot 0 Column 1 Offset 0x16 Length 11 activity = sightseeing Slot 0 Column 2 Offset 0x4 Length 4 duration = 5

Ранее я уже писал о структуре записей, сейчас повторюсь:

  • Заголовок записи

    • Длина заголовка - 4 байта;

    • Два байта метаданных записи (тип записи);

    • Два байта указывают вначале записи на битовую маску NULL.

  • Фиксированная часть длины записи - содержит хранимые типы столбцов данных, которые имеют фиксированную длину (например: bigint, char (10), datetime).

  • Битовая маска NULL

    • Два байта для счетчика столбцов записи;

    • Переменное число байт для хранения одного бита на каждый столбец записи, независимо от того, допускаются ли для этого столбца NULL-значения (это отличается от того, что было в SQL Server 2000, в котором отводилось по одному биту для nullable столбцов) и является более простым решением;

    • Эта маска позволяет оптимизировать чтение столбцов, которые имеют NULL-значения.

  • Массив смещения для столбцов переменной длины

    • Два байта счётчика столбцов переменной длины;

    • Два байта на каждый столбец переменной длины, задающие смещение на начало значения столбца.

  • Тэг версионности

    • Появился в SQL Server 2005;

    • Является 14-байтной структурой, которая содержит временную метку и указатель в хранилище версий в tempdb.

Итак, давайте посмотрим на данные из фрагмента дампа и выясним, что все это означает. Я не собираюсь вдаваться во все подробности относительно каждого возможного значения каждого байта, но дам достаточно информации, чтобы Вы могли самостоятельно расшифровать полученные данные.
Байт 0 - это байт TagA записи метаданных. 0х30 соответствует 0х10 (бит 4) и 0х20 (бит 5). Бит 4 означает, что запись имеет битовую маску NULL, а бит 5, что запись имеет поля переменной длины. Если установлено также 0х40 (бит 6), это означает что запись имеет тег версионности. Если установлено 0х80 (бит 7), то первый байт содержит в себе значение. Биты 1-3 байта 0 содержат тип записи. Они могут принимать следующие значения:

    0 = primary record (первичная запись)

    • Запись данных в куче, которая не была перемещена, или запись данных на листовом уровне кластерного индекса.

    1 = forwarded record (перемещенная запись)

    • Запись данных в куче, которая была модифицирована, не поместилась на странице, где она размещалась изначально, и поэтому была перемещена на другую страницу. Перемещаемая запись меняет свое местоположение и вместо неё остаётся указатель на новое положение записи. Это сделано для того, что бы не вносить изменения в записи некластеризованных индексов, которые указывают непосредственно на первоначальное физическое местоположение записи.

    2 = forwarding record (перемещаемая запись)

    • Иногда такие записи называют перемещаемым остатком.

    3 = index record (индексные записи)

    • Индексные записи в дереве кластеризованного индекса, или на любом уровне некластеризованного индекса

    4 = фрагмент BLOB'а

    5 = фантомные индексные записи

    6 = фантомные записи данных

    7 = фантомные записи версионности

    • Специальные 15 - байтовые записи, в которых один байт занимает заголовок, а 14 байт содержат тег версионности. Используются при некоторых обстоятельствах (например, при фантомности версионных blob записей).

В нашем примере, ни один из этих битов не установлен, что означает, что мы имеем дело с первичной записью. Если бы запись была индексной, байт 0 имел бы значение 0x36. Помните, что тип записи начинается с бита 1, а не с бита 0, поэтому значение типа записи, приведенное выше нужно сдвинуть влево на 1 бит (умножить на два) чтобы получить его значение в байте.

Байт 1 это байт TagB записи метаданных. Он может быть 0х00 или 0х01. Если он 0х01, это означает что тип записи - фантомная перемещенная запись. В нашем случае он 0х00, что и можно было предположить по значению TagA.

Байты 2 и 3 - это смещение битовой маски NULL в записи. Оно равно 0х0008 (DBCC PAGE представляет много-байтовые значения в шестнадцатеричном виде, где вначале идет первый байт). Это означает, что имеется 4-байтовая фиксированная часть длины записи, начинающейся с байта 4. Мы ожидали этого, поскольку знаем схему таблицы.

Байты с 4 по 7 - фиксированная часть длины. И снова, поскольку мы знаем схему таблицы, мы знаем, что они интерпретируются как 4-байтовое целое (integer). Если бы мы не знали этого, то могли бы предположить. Поэтому значение - 0x00000005- это то, что мы ожидали увидеть в качестве значения столбца duration.

Байты 8 и 9 содержат количество полей в записи. Наблюдаемое значение 0x0003 несомненно является правильным. Учитывая, что имеются только 3 столбца, битовая маска NULL, каждый бит которой соответствует одному из столбцов, будет занимать один байт.

Байт 10 - это битовая маска NULL. У нас это значение 0xF8 - откуда оно взялось? Преобразуйте его к двоичному виду, что получится? 11111000. Все становится понятно - биты 0-2 представляют столбцы 1-3, и они - все 0, это означает что столбцы не NULL. Биты 3-7 представляют несуществующие столбцы, и они установлены в 1 (для ясности). Так что значение 0xF8 имеет смысл.

Байты 11 и 12 содержат количество в записи столбцов переменной длины. Это значение - 0x0002, которое как можно догадаться, является правильным. Это означает, что имеются два двухбайтовых элемента в массиве смещений столбцов переменной длины. Это будут байты 13-14 и 15-16, которым соответствуют значения 0x0016 и 0x0021 соответственно.
Поясню, проще говоря, массив смещений столбцов переменной длины сохраняет смещения на начало значения столбца. Фактически, элементы массива указывают на начало следующего значения столбца - это сделано для того, чтобы мы знали длину каждого столбца (т.к. они переменной длины). Обратите внимание, что смещение первого значения столбца переменной длины не сохранено - этого не требуется, потому что по определению оно должно начинаться справа после последнего смещения в массиве смещений столбцов переменной длины.
Итак, последнее смещение - байты 15 и 16, означают, что смещение начала первого столбца переменной длины должно быть 17 байт (или 0x11 в шестнадцатеричном виде), что соответствует результату, полученному с помощью DBCC PAGE. Смещение второго столбца переменной длины - 0x0016, так что первое значение - от байта 17 до байта 21 включительно. Это значение - 0x42616E6666. Мы знаем из метаданных таблицы, что это - первый столбец типа varchar - destination. Воспользовавшись вполне удобной таблицей ASCII преобразований http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters мы легко вычисляем, что это транслируется как 'Banff'.
Используя такую же логику, второе значение - это байты с 22 по 32 включительно, имеет значение 'sightseeing'. Оба этих значения мы и ожидали увидеть.

Вот и все. Мы только что расшифровали всю запись. Теперь попробуйте сделать это со второй строкой таблицы.

[В начало]

Перевод: Ирины Наумовой  2007г.

Rambler's Top100 Рейтинг@Mail.ru  Administrator: Обратная связь 
Copyright: SQL.Ru 2000-2013