Прежде чем переходить к механизму работы, хочу разобрать две команды, которые я буду часто использовать — DBCC PAGE
и DBCC IND
. Обе они (на момент написания статьи) являются недокументированными и не поддерживаются официально, но абсолютно безопасны в применении, так как активно используются внутри и вне Microsoft при диагностике. Тем не менее, используйте их на свой страх и риск. Эти команды хорошо известны в сообществе MSSQL, я и другие специалисты уже рассказывали о них ранее.
Чтобы показать, как они работают, я приведу простой сценарий, доказывающий, что расщепления страниц никогда не откатываются. У меня был спор на эту тему, и ответ всегда один — нет. Расщепление страницы происходит тогда, когда вставка или обновление должны произойти в определённом месте индексной страницы, но там не хватает места для новой или обновлённой записи. Расщепления выполняются как отдельные «системные» транзакции. После того как системная транзакция зафиксирована, она не может быть отменена — даже если откатывается пользовательская транзакция, частью которой она была.
Сначала создадим базу данных с таблицей и индексом (так как расщепления происходят только в индексах).
USE MASTER;
GO
IF DATABASEPROPERTY (N'pagesplittest', 'Version') > 0 DROP DATABASE pagesplittest;
GO
CREATE DATABASE pagesplittest;
GO
USE pagesplittest;
GO
CREATE TABLE t1 (c1 INT, c2 VARCHAR (1000));
CREATE CLUSTERED INDEX t1c1 ON t1 (c1);
GO
Теперь заполним индексную страницу так, чтобы осталось «окно» для вставки недостающего значения и можно было вызвать расщепление.
INSERT INTO t1 VALUES (1, REPLICATE('a', 900));
INSERT INTO t1 VALUES (2, REPLICATE('b', 900));
INSERT INTO t1 VALUES (3, REPLICATE('c', 900));
INSERT INTO t1 VALUES (4, REPLICATE('d', 900));
-- пропуск значения 5
INSERT INTO t1 VALUES (6, REPLICATE('f', 900));
INSERT INTO t1 VALUES (7, REPLICATE('g', 900));
INSERT INTO t1 VALUES (8, REPLICATE('h', 900));
INSERT INTO t1 VALUES (9, REPLICATE('i', 900));
GO
Использование DBCC IND
Чтобы узнать, какая страница является первой в индексе, используем команду DBCC IND
:
DBCC IND ('pagesplittest', 't1', 1);
GO
Эта команда выводит список всех страниц, выделенных под индекс. Пример вывода:
Расшифровка колонок:
- PageFID — идентификатор файла страницы
- PagePID — номер страницы в файле
- IAMFID — идентификатор файла страницы IAM
- IAMPID — номер страницы IAM
- ObjectID — идентификатор объекта
- IndexID — идентификатор индекса
- PartitionNumber — номер секции
- PartitionID — внутренний ID секции
- iam_chain_type — тип IAM-цепочки
- PageType — тип страницы (1 — данные, 2 — индекс, 8 — GAM, 9 — SGAM, 10 — IAM, 11 — PFS и т. д.)
- IndexLevel — уровень страницы в индексе (0 — листья, N — корень)
- NextPageFID/NextPagePID, PrevPageFID/PrevPagePID — ссылки на соседние страницы
Видим, что у нас одностраничный кластеризованный индекс с IAM-страницей. Заметьте, что ID страниц у вас могут быть другими. Давайте посмотрим данные страницы:
DBCC TRACEON (3604);
GO
DBCC PAGE (pagesplittest, 1, 143, 3);
GO
Флаг трассировки позволяет выводить данные команды DBCC PAGE на консоль, а не в журнал ошибок. Синтаксис команды DBCC PAGE:
dbcc page ( {‘dbname’ | dbid}, filenum, pagenum [, printopt={0|1|2|3} ])
Параметры filenum и pagenum берутся из идентификаторов страниц, которые берутся из различных системных таблиц и отображаются в DBCC или других системных сообщениях об ошибках. Пот пример расшифровки идентификатора страницы: (1:143) будет filenum = 1 и pagenum = 143.
Параметр printopt имеет следующие значения:
0 — вывести только заголовок страницы
1 — заголовок страницы плюс шестнадцатеричные дампы для каждой строки и дамп массива слотов страницы (если только это не страница без слота, например, битовая карта распределения)
2 — заголовок страницы плюс шестнадцатеричный дамп всей страницы
3 — заголовок страницы плюс подробное описание каждой строки
Интерпретация по строкам работает для всех типов страниц, включая битовые карты распределения. В нашем случае мы запросили подробный вывод. Я объясню, что означают различные части вывода, в посте об анатомии страницы. Вот вывод команды DBCC PAGE, из которого для краткости удалена часть повторяющейся информации по строкам:
Вывод показывает, что каждая строка занимает 917 байт, свободно только 744 байта (m_freecnt
в заголовке страницы). Значит, вставить новую строку аналогичной длины на эту страницу невозможно — но именно это мы и собираемся сделать! Помните, что на этой странице сейчас девять строк. Вызовем расщепление:
BEGIN TRAN;
GO
INSERT INTO t1 VALUES (5, REPLICATE('a', 900));
GO
Мы знаем, что места не было, значит страница должна была расщепиться. Проверим снова DBCC IND
:
Добавились две страницы — одна индексная и ещё одна страница данных. До этого индексу требовалась только одна страница, теперь — две. Поэтому создаётся индексная страница для поддержки поиска. Анализ страниц показывает: страница (1:143) содержит значения с1 от 1 до 5, страница (1:154) — значения с1 от 6 до 9. Это ожидаемо: расщепление произошло в точке вставки, и новая строка оказалась на расщепившейся странице.
Теперь откатим транзакцию:
ROLLBACK TRAN;
GO
После отката структура индекса остаётся такой же, как после расщепления. Страница (1:154) хранит строки, полученные при расщеплении, а страница (1:143) содержит остальные строки, но без той, что была вставлена в рамках транзакции.
Таким образом, мы доказали: расщепление страниц никогда не откатывается.
Комментариев нет:
Отправить комментарий