5.3.26

Почему LOB-колонки делают SHRINK очень медленным

Автор: Paul Randal, Why LOB data makes shrink run slooooowly (T-SQL Tuesday #006)

[Примечание от января 2015: Всё, что написано в этом посте, по-прежнему актуально для SQL Server 2012 и 2014.]

Тема этой статьи — LOB-данные, поэтому я немного отклонюсь от темы и объясню, почему LOB-данные делают производительность сжатия (shrink) действительно отвратительной (это технический термин :-).

Вы все знаете, что я действительно не люблю сжатие файлов данных (хотя иногда это может быть абсолютно необходимо. Смотрите эти мои проповеди на эту тему):

Сжатие файла данных работает следующим образом: оно выбирает страницы из конца файла данных и пытается переместить их как можно ближе к началу файла данных. В процессе этого оно должно исправить любые структуры, частью которых является эта страница.

Например, если страница является частью индекса, в индексе есть другие страницы, которые имеют физические ссылки на перемещаемую страницу — эти связи необходимо исправить. Соседние страницы слева и справа на том же уровне индекса исправить легко, так как перемещаемая страница указывает на них.

Однако страница на следующем уровне вверх к корневой странице индекса является более сложной, так как нет обратной ссылки, указывающей на неё, — требуется сканирование следующего уровня индекса, чтобы найти страницу, которая указывает вниз на перемещаемую страницу. Это должно быть довольно быстро, так как уровни индекса становятся намного меньше по мере продвижения вверх к корневой странице (величина изменения размера зависит от степени ветвления индекса — я объясняю это в посте sys.dm_db_index_physical_stats со всеми "потрохами").

Важный момент: Всякий раз, когда страница перемещается и есть некоторая часть структуры на странице, у которой нет обратной ссылки на то, что на неё указывает, требуется сканирование.

Значения LOB могут храниться внутри строки (in-row) или вне строки (off-row) (т.е. как часть записи данных или индекса, или в дереве текстовых записей на совершенно других страницах). Устаревшие типы LOB (text, ntext, image) по умолчанию хранятся вне строки. Новые типы LOB (varchar(max), nvarchar(max), XML, varbinary(max)) по умолчанию хранятся внутри строки, максимальным размером до 8000 байт, если в записи есть место.

Когда значение LOB хранится вне строки, в строке данных или индекса хранится сложный указатель (называемый корнем LOB-объекта — blob root), который содержит указатель на физическое расположение вершины дерева текстовых записей, составляющих значение LOB, размер первой записи, на которую указывает указатель, и временную метку (не время, а значение типа данных timestamp). Текстовая запись, на которую указывают, также содержит ту же временную метку.

Давайте проверим:

CREATE TABLE TextTest (c1 INT, c2 TEXT); GO INSERT INTO TextTest VALUES (1, 'a'); GO

Теперь найдём первую страницу в таблице с помощью DBCC IND, моего скрипта sp_AllocationMetadata или чего-то ещё и выполним DBCC PAGE на ней:

DBCC TRACEON (3604);
DBCC PAGE (TestDB, 1, 737, 3);
GO
<snip>

Slot 0 Column 0 Offset 0x4 Length 4
c1 = 1                              
c2 = [Textpointer] Slot 0 Column 1 Offset 0xf Length 16
TextTimeStamp = 131137536            RowId = (1:152:0)

Если мы перейдём по указателю на страницу (1:152) и выполним на ней DBCC PAGE, мы увидим:

DBCC TRACEON (3604);
DBCC PAGE (foo, 1, 152, 3);
GO
<snip>

Blob row at: Page (1:152) Slot 0 Length: 84 Type: 0 (SMALL_ROOT)
Small Blob Id: 131137536 Size:1
6855C074:        61                               a

Обратите внимание, что два выделенных жирным шрифтом числа совпадают. Также обратите внимание, что нет обратной ссылки от значения LOB, хранящегося вне строки, обратно к «владеющей» записи данных.

Когда страница текста перемещается сжатием файла данных, для каждого значения LOB должно выполняться сканирование таблицы, чтобы найти «владеющую» запись данных, чтобы указатель мог быть обновлён. (Если значение LOB происходит из столбца, включённого (INCLUDED) в некластерный индекс, то аналогичное сканирование должно выполняться для некластерного индекса. Если значение LOB находится ниже в дереве текста для значения LOB размером >8000 байт, все текстовые страницы для этой таблицы или индекса должны быть просканированы.)

Очень медленно. Очень-очень медленно.

Похожая проблема возникает, если DBCC CHECKDB находит потерянную текстовую запись (orphaned text record) — он не может определить, кто должен быть её владельцем, поэтому ему приходится возвращаться и повторно сканировать все таблицы в текущем пакете, чтобы выяснить это, — что может привести к тому, что DBCC CHECKDB будет выполняться намного дольше, чем обычно.

Во время разработки SQL Server 2005, когда были реализованы новые типы LOB, я настаивал на добавлении обратной ссылки для облегчения этих проблем, но инженерные усилия не оправдывали себя. Так что мы застряли с этим.

Ещё одна причина, по которой сжатие ужасно!

Полезная информация: KB2967240 — FIX: не удается восстановить неиспользуемое пространство с помощью операции сжатия в таблице, содержащей столбец LOB, в SQL Server


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

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