14.10.25

Структура каталога FILESTREAM

Автор: Paul Randal, FILESTREAM directory structure

После того как я написал FILESTREAM Whitepaper для Microsoft, мне стали часто задавать вопросы о структуре контейнера данных FILESTREAM. Контейнер данных FILESTREAM — это технический термин для структуры каталогов NTFS, где хранятся все данные FILESTREAM.

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

ALTER DATABASE [FileStreamTestDB] ADD FILEGROUP [FileStreamGroup1] CONTAINS FILESTREAM;
GO

Затем добавить «файл» в файловую группу:

ALTER DATABASE [FileStreamTestDB] ADD FILE (
  NAME = [FSGroup1File], FILENAME = N'C:\Metro Labs\FileStreamTestDB\Documents')
TO FILEGROUP [FileStreamGroup1];
GO

На самом деле «файл» — это путь к тому, что станет корневым каталогом контейнера данных FILESTREAM. При первоначальном создании он будет содержать один файл filestream.hdr и один каталог _$FSLOG_. Файл filestream.hdr — это метаданные, описывающие контейнер данных, а каталог _$FSLOG_ — FILESTREAM‑аналог журнала транзакций базы данных. Их можно считать концептуально эквивалентными, хотя у журнала FILESTREAM есть интересные особенности.

Чаще всего меня спрашивают: хранятся ли все файлы FILESTREAM для базы данных в одном огромном каталоге? Ответ: нет.

Корневой каталог контейнера данных содержит по одному подкаталогу на каждую таблицу (или на каждый раздел, если таблица секционирована). Каждый из этих каталогов содержит ещё один подкаталог для каждого FILESTREAM‑столбца, определённого в таблице. Ниже приведён пример; снимок экрана был сделан после выполнения следующего кода:

CREATE TABLE [FileStreamTest1] (
    [DocId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE,
    [DocName] VARCHAR(25),
    [Document] VARBINARY(MAX) FILESTREAM);
GO
CREATE TABLE [FileStreamTest2] (
    [DocId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE,
    [DocName] VARCHAR(25),
    [Document1] VARBINARY(MAX) FILESTREAM,
    [Document2] VARBINARY(MAX) FILESTREAM);
GO
INSERT INTO [FileStreamTest1] VALUES (NEWID(), 'Paul Randal', CAST('SQLskills.com' AS VARBINARY(MAX)));
INSERT INTO [FileStreamTest1] VALUES (NEWID(), 'Kimberly Tripp', CAST('SQLskills.com' AS VARBINARY(MAX)));
GO

Это изображение показывает контейнер данных FILESTREAM для нашей базы данных, в которой две таблицы со столбцами FILESTREAM, каждая — с одним разделом. Первая таблица имеет два FILESTREAM‑столбца, а вторая — один. Имена всех этих каталогов — GUID’ы; как сопоставить каталоги таблицам и столбцам, я объясняю в отдельной статье.

В примере можно увидеть два FILESTREAM‑файла в каталоге уровня столбца. Имена файлов FILESTREAM — это, по сути, номер LSN из журнала транзакций базы данных на момент создания файлов. Это можно сопоставить, просмотрев данные с помощью DBCC PAGE; но сперва нужно найти выделенные страницы, используя sp_AllocationMetadata (см. соответствующую запись в блоге):

EXEC sp_AllocationMetadata FileStreamTest1;
GO
Object Name     Index ID Alloc Unit ID     Alloc Unit Type   First Page Root Page First IAM Page
--------------- -------- ----------------- ---------------   ---------- --------- --------------
FileStreamTest1 0        72057594039697408 IN_ROW_DATA       (1:169)    (0:0)     (1:170)
FileStreamTest1 0        72057594039762944 ROW_OVERFLOW_DATA (0:0)      (0:0)     (0:0)
FileStreamTest1 2        72057594039828480 IN_ROW_DATA       (1:171)    (1:171)   (1:172)

Обратите внимание: помимо кучи есть и некластеризованный индекс — это индекс, обеспечивающий уникальность в столбце UNIQUEIDENTIFIER. Теперь можно использовать DBCC PAGE, чтобы посмотреть первую страницу кучи, на которой находятся наши записи данных:

DBCC TRACEON (3604);
DBCC PAGE (FileStreamTestDB, 1, 169, 3);
GO
(snip)
Slot 0 Offset 0x60 Length 88
Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS
Record Size = 88
Memory Dump @0x514EC060
00000000:   30001400 140d5047 2ca9f24f 874d35ca †0.....PG,©òO‡M5Ê
00000010:   e9e77649 03000002 00280058 80506175 †éçvI.....(.X.Pau
00000020:   6c205261 6e64616c 03000000 00000080 †l Randal........
00000030:   140d5047 2ca9f24f 874d35ca e9e77649 †..PG,©òO‡M5ÊéçvI
00000040:   01000000 68020000 00000000 17000000 †....h...........
00000050:   79000000 0c000000 †††††††††††††††††††y.......
Slot 0 Column 1 Offset 0x4 Length 16 Length (physical) 16
DocId = 47500d14-a92c-4ff2-874d-35cae9e77649
Slot 0 Column 2 Offset 0x1d Length 11 Length (physical) 11
DocName = Paul Randal
Document = [Filestream column] Slot 0 Column 3 Offset 0x28 Length 48
ColType = 3                          FileId = -2147483648                 UpdateSeq = 1
CreateLSN = 00000017:00000079:000c (23:121:12)                            TxFMiniVer = 0
XdesId = (0:616)
(snip)

Видно, что значение CreateLSN выше совпадает с именем первого FILESTREAM‑файла на иллюстрации к примеру.

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




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

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